CMS Project Sync
This commit is contained in:
+833
@@ -0,0 +1,833 @@
|
||||
import { SMUSH_BEFORE_SIZES, LAZY_BEFORE_SIZES, AutoResizing } from '../frontend/lazy-load/auto-resizing';
|
||||
import { isSmushLazySizesInstance } from '../frontend/lazy-load/helper/lazysizes';
|
||||
|
||||
jest.mock( '../frontend/lazy-load/helper/lazysizes', () => ( {
|
||||
isSmushLazySizesInstance: jest.fn(),
|
||||
} ) );
|
||||
|
||||
describe( 'AutoResizing', () => {
|
||||
let instance;
|
||||
let originalDevicePixelRatio;
|
||||
|
||||
beforeEach( () => {
|
||||
document.body.innerHTML = ''; // Clear DOM
|
||||
originalDevicePixelRatio = window.devicePixelRatio;
|
||||
isSmushLazySizesInstance.mockReset();
|
||||
instance = new AutoResizing( { precision: 5, skipAutoWidth: true } );
|
||||
} );
|
||||
|
||||
afterEach( () => {
|
||||
window.matchMedia = undefined;
|
||||
window.devicePixelRatio = originalDevicePixelRatio;
|
||||
} );
|
||||
|
||||
test( 'initializes with default values', () => {
|
||||
const auto = new AutoResizing();
|
||||
expect( auto.precision ).toBe( 0 );
|
||||
expect( auto.skipAutoWidth ).toBe( false );
|
||||
} );
|
||||
|
||||
test.each([
|
||||
[
|
||||
'Smush LazySizes instance is valid',
|
||||
true,
|
||||
'https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=500x0 500w'
|
||||
],
|
||||
[
|
||||
'Smush LazySizes instance is invalid',
|
||||
false,
|
||||
'https://smushcdn.com/img-300x300.jpg 300w'
|
||||
]
|
||||
])( 'Should handle LAZY_BEFORE_SIZES event based on Smush LazySizes instance: %s', ( description, isValidLazyInstance, expectedSrcset ) => {
|
||||
isSmushLazySizesInstance.mockReturnValue( isValidLazyInstance );
|
||||
|
||||
const imageMarkup = `<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px">`;
|
||||
const container = document.createElement( 'div' );
|
||||
container.innerHTML = imageMarkup;
|
||||
const img = container.firstElementChild;
|
||||
// Appending the image into document to ensures the event can propagate through the DOM tree,
|
||||
// allowing listeners on document to catch it.
|
||||
document.body.appendChild( img );
|
||||
|
||||
const event = new CustomEvent( LAZY_BEFORE_SIZES, {
|
||||
detail: { instance: {}, width: 500, dataAttr: true },
|
||||
bubbles: true,
|
||||
} );
|
||||
Object.defineProperty( event, 'target', { value: img } );
|
||||
document.dispatchEvent( event );
|
||||
|
||||
const srcset = img.getAttribute( 'data-srcset' );
|
||||
expect( srcset ).toBe( expectedSrcset );
|
||||
} );
|
||||
|
||||
test.each( [
|
||||
[
|
||||
'skips resizing when width is invalid',
|
||||
'img', null, true, 'https://smushcdn.com/img-300x300.jpg 300w'
|
||||
],
|
||||
[
|
||||
'skips resizing when not initial render',
|
||||
'img', 500, false, 'https://smushcdn.com/img-300x300.jpg 300w'
|
||||
],
|
||||
[
|
||||
'performs resizing for valid image and initial render',
|
||||
'img', 500, true, 'https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=500x0 500w'
|
||||
],
|
||||
[
|
||||
'skips resizing when element is not an image',
|
||||
'div', 500, true, 'https://smushcdn.com/img-300x300.jpg 300w'
|
||||
],
|
||||
] )( 'skips processing when element is not image or missing width, dataAttr: %s', ( description, tagName, resizeWidth, isInitialRender, expectedSrcset ) => {
|
||||
const element = document.createElement( tagName );
|
||||
element.setAttribute( 'data-src', 'https://smushcdn.com/img.jpg' );
|
||||
element.setAttribute( 'data-srcset', 'https://smushcdn.com/img-300x300.jpg 300w' );
|
||||
element.setAttribute( 'data-original-sizes', '(max-width: 1024px) 100vw, 1024px' );
|
||||
document.body.appendChild( element );
|
||||
|
||||
const event = new CustomEvent( LAZY_BEFORE_SIZES, {
|
||||
detail: { instance: {}, width: resizeWidth, dataAttr: isInitialRender },
|
||||
bubbles: true,
|
||||
} );
|
||||
Object.defineProperty( event, 'target', { value: element } );
|
||||
|
||||
isSmushLazySizesInstance.mockReturnValue( true );
|
||||
document.dispatchEvent( event );
|
||||
|
||||
const srcset = element.getAttribute( 'data-srcset' );
|
||||
expect( srcset ).toBe( expectedSrcset );
|
||||
} );
|
||||
|
||||
test.each( [
|
||||
[
|
||||
'Image element eligible for resizing',
|
||||
`<img data-src="https://example.com/img.jpg"
|
||||
data-srcset="https://example.com/img.jpg 300w"
|
||||
data-sizes="auto"
|
||||
data-original-sizes="(max-width: 400px) 100vw, 400px"
|
||||
width="350" >`,
|
||||
true
|
||||
],
|
||||
[
|
||||
'Image element NOT eligible for resizing due to missing data-src',
|
||||
`<img src="https://example.com/img.jpg"
|
||||
data-srcset="https://example.com/img.jpg 300w"
|
||||
data-sizes="auto"
|
||||
data-original-sizes="(max-width: 400px) 100vw, 400px"
|
||||
width="350" >`,
|
||||
false
|
||||
],
|
||||
[
|
||||
'Image element NOT eligible for resizing due to missing data-srcset',
|
||||
`<img data-src="https://example.com/img.jpg"
|
||||
srcset="https://example.com/img.jpg 300w"
|
||||
data-sizes="auto"
|
||||
data-original-sizes="(max-width: 400px) 100vw, 400px"
|
||||
width="350" >`,
|
||||
false
|
||||
],
|
||||
[
|
||||
'Image element NOT eligible for resizing due to missing data-original-sizes',
|
||||
`<img data-src="https://example.com/img.jpg"
|
||||
srcset="https://example.com/img.jpg 300w"
|
||||
data-sizes="auto"
|
||||
width="350" >`,
|
||||
false
|
||||
],
|
||||
] )( 'test isElementEligibleForResizing: %s', ( description, imageMarkup, expectedResult ) => {
|
||||
const container = document.createElement( 'div' );
|
||||
container.innerHTML = imageMarkup;
|
||||
const img = container.firstElementChild;
|
||||
|
||||
expect( instance.isElementEligibleForResizing( img ) ).toBe( expectedResult );
|
||||
} );
|
||||
|
||||
test.each( [
|
||||
[
|
||||
'Image has max width smaller than resizing width, revert to original sizes.',
|
||||
`<img data-src="https://example.com/img.jpg"
|
||||
data-srcset="https://example.com/img.jpg 300w"
|
||||
data-sizes="auto"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px">`,
|
||||
'(max-width: 300px) 100vw, 300px'
|
||||
],
|
||||
[
|
||||
'Image has max width equal resizing width does not revert sizes.',
|
||||
`<img data-src="https://example.com/img.jpg"
|
||||
data-srcset="https://example.com/img.jpg 900w"
|
||||
data-sizes="auto"
|
||||
data-original-sizes="(max-width: 350px) 100vw, 350px">`,
|
||||
null
|
||||
],
|
||||
[
|
||||
'Image has width auto, revert to original sizes.',
|
||||
`<img data-src="https://example.com/img.jpg"
|
||||
data-srcset="https://example.com/img.jpg 400w"
|
||||
data-sizes="auto"
|
||||
data-original-sizes="(max-width: 400px) 100vw, 400px"
|
||||
style="width:auto" >`,
|
||||
'(max-width: 400px) 100vw, 400px'
|
||||
],
|
||||
[
|
||||
'Valid resizing image, keep using data-size="auto".',
|
||||
`<img data-src="https://example.com/img.jpg"
|
||||
data-srcset="https://example.com/img.jpg 400w"
|
||||
data-sizes="auto"
|
||||
data-original-sizes="(max-width: 400px) 100vw, 400px" >`,
|
||||
null
|
||||
],
|
||||
[
|
||||
'Image with no data-original-sizes does not revert sizes.',
|
||||
`<img data-src="https://example.com/img.jpg"
|
||||
data-srcset="https://example.com/img.jpg 300w"
|
||||
data-sizes="auto" style="width:auto">`,
|
||||
null
|
||||
],
|
||||
] )( 'reverts to original sizes if applicable: %s', ( description, imageMarkup, expectedSizes ) => {
|
||||
const container = document.createElement( 'div' );
|
||||
container.innerHTML = imageMarkup;
|
||||
const img = container.firstElementChild;
|
||||
const isRevertedToOriginalSizes = Boolean( expectedSizes );
|
||||
|
||||
const preventDefault = jest.fn();
|
||||
const event = {
|
||||
detail: { instance: {}, width: 350, dataAttr: true },
|
||||
target: img,
|
||||
preventDefault,
|
||||
};
|
||||
|
||||
instance.maybeAutoResize( event );
|
||||
|
||||
expect( img.getAttribute( 'sizes' ) ).toBe( expectedSizes );
|
||||
expect( preventDefault ).toHaveBeenCalledTimes( isRevertedToOriginalSizes ? 1 : 0 );
|
||||
} );
|
||||
|
||||
test( 'Revert original sizes via custom event', () => {
|
||||
isSmushLazySizesInstance.mockReturnValue( true );
|
||||
|
||||
const img = document.createElement( 'img' );
|
||||
img.setAttribute( 'data-original-sizes', '(max-width: 400px) 100vw, 400px' );
|
||||
img.setAttribute( 'data-src', 'https://example.com/img.jpg?size=400x0' );
|
||||
img.setAttribute( 'data-srcset', 'https://example.com/img.jpg 400w' );
|
||||
img.setAttribute( 'data-sizes', 'auto' );
|
||||
// Appending the image into document to ensures the event can propagate through the DOM tree,
|
||||
// allowing listeners on document to catch it.
|
||||
document.body.appendChild( img );
|
||||
|
||||
// Listen and prevent default on the custom event
|
||||
const handler = function( e ) {
|
||||
e.preventDefault();
|
||||
};
|
||||
document.addEventListener( SMUSH_BEFORE_SIZES, handler );
|
||||
|
||||
// Create a real CustomEvent
|
||||
const event = new CustomEvent( LAZY_BEFORE_SIZES, {
|
||||
detail: { instance: {}, width: 350, dataAttr: true },
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
} );
|
||||
Object.defineProperty( event, 'target', { value: img } );
|
||||
|
||||
// Call the method that triggers the event internally
|
||||
document.dispatchEvent( event );
|
||||
|
||||
expect( img.getAttribute( 'sizes' ) ).toBe( '(max-width: 400px) 100vw, 400px' );
|
||||
expect( img.getAttribute( 'data-srcset' ) ).toBe( 'https://example.com/img.jpg 400w' );
|
||||
|
||||
document.removeEventListener( SMUSH_BEFORE_SIZES, handler );
|
||||
} );
|
||||
|
||||
const skippedImages = [
|
||||
[
|
||||
'Image missing data-src',
|
||||
`<img src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
'https://smushcdn.com/img-300x300.jpg 300w'
|
||||
],
|
||||
[
|
||||
'Image missing data-srcset',
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
''
|
||||
],
|
||||
[
|
||||
'Image skipped due to source is not from Smush CDN',
|
||||
`<img src="https://example.com/img.jpg"
|
||||
data-srcset="https://example.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
'https://example.com/img-300x300.jpg 300w'
|
||||
],
|
||||
[
|
||||
'Image skipped due to has similar source in srcset',
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=605x0 605w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
'https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=605x0 605w'
|
||||
],
|
||||
];
|
||||
|
||||
const autoResizedImages = [
|
||||
[
|
||||
'Resizes CDN image and appends new srcset entry for requested width',
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
'https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=600x0 600w'
|
||||
],
|
||||
[
|
||||
'Adds new CDN srcset entry for 600w since it is not within precision (5) of existing 594w',
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=594x0 594w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
'https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=594x0 594w, https://smushcdn.com/img.jpg?size=600x0 600w'
|
||||
],
|
||||
[
|
||||
'Adds new CDN srcset entry for 600w since similar source in srcset smaller than requested width',
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
'https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w, https://smushcdn.com/img.jpg?size=600x0 600w'
|
||||
],
|
||||
];
|
||||
|
||||
test.each( [
|
||||
...skippedImages,
|
||||
...autoResizedImages,
|
||||
] )( 'test resizeImageWithCDN: %s', ( description, imageMarkup, expectedSrcset ) => {
|
||||
const container = document.createElement( 'div' );
|
||||
container.innerHTML = imageMarkup;
|
||||
const img = container.firstElementChild;
|
||||
|
||||
const event = {
|
||||
detail: { width: 600, instance: {}, dataAttr: true },
|
||||
target: img,
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
|
||||
instance.maybeAutoResize( event );
|
||||
|
||||
const newSrcset = img.getAttribute( 'data-srcset' ) || '';
|
||||
expect( newSrcset ).toBe( expectedSrcset );
|
||||
} );
|
||||
|
||||
const autoResizedImagesWithRetina = [
|
||||
[
|
||||
'Resizes CDN image and appends new srcset entry for requested width',
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=315x0 315w, https://smushcdn.com/img.jpg?size=552x0 552w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
],
|
||||
[
|
||||
'Appends new CDN srcset entry for requested width, preserving existing retina source',
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=552x0 552w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=552x0 552w, https://smushcdn.com/img.jpg?size=315x0 315w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >`,
|
||||
],
|
||||
[
|
||||
'Adds both standard and retina srcset entries when original sizes match requested width',
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 315px) 100vw, 315px" >`,
|
||||
`<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=315x0 315w, https://smushcdn.com/img.jpg?size=552x0 552w"
|
||||
data-original-sizes="(max-width: 315px) 100vw, 315px" >`,
|
||||
],
|
||||
[
|
||||
'Appends new CDN srcset entry to all responsive <source> elements in a <picture> element',
|
||||
`<picture>
|
||||
<source
|
||||
media="(min-width: 800px)" type="image/webp"
|
||||
data-srcset="https://smushcdn.com/medium-large.webp 800w, https://smushcdn.com/large.webp 1024w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source
|
||||
media="(min-width: 800px)" type="image/jpeg"
|
||||
data-srcset="https://smushcdn.com/medium-large.jpg 800w, https://smushcdn.com/large.jpg 1024w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source
|
||||
media="(max-width: 799px)" type="image/webp"
|
||||
data-srcset="https://smushcdn.com/medium.webp 500w, https://smushcdn.com/medium-smaller.webp 400w"
|
||||
data-sizes="100vw">
|
||||
<source
|
||||
media="(max-width: 799px)" type="image/jpeg"
|
||||
data-srcset="https://smushcdn.com/medium.jpg 500w, https://smushcdn.com/medium-smaller.jpg 400w"
|
||||
data-sizes="100vw">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source media="(min-width: 800px)"
|
||||
type="image/webp"
|
||||
data-srcset="https://smushcdn.com/medium-large.webp 800w, https://smushcdn.com/large.webp 1024w, https://smushcdn.com/large.webp?size=315x0 315w, https://smushcdn.com/large.webp?size=552x0 552w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source media="(min-width: 800px)"
|
||||
type="image/jpeg"
|
||||
data-srcset="https://smushcdn.com/medium-large.jpg 800w, https://smushcdn.com/large.jpg 1024w, https://smushcdn.com/large.jpg?size=315x0 315w, https://smushcdn.com/large.jpg?size=552x0 552w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source
|
||||
media="(max-width: 799px)"
|
||||
type="image/webp"
|
||||
data-srcset="https://smushcdn.com/medium.webp 500w, https://smushcdn.com/medium-smaller.webp 400w, https://smushcdn.com/medium.webp?size=315x0 315w, https://smushcdn.com/medium.webp?size=552x0 552w"
|
||||
data-sizes="100vw">
|
||||
<source
|
||||
media="(max-width: 799px)"
|
||||
type="image/jpeg"
|
||||
data-srcset="https://smushcdn.com/medium.jpg 500w, https://smushcdn.com/medium-smaller.jpg 400w, https://smushcdn.com/medium.jpg?size=315x0 315w, https://smushcdn.com/medium.jpg?size=552x0 552w"
|
||||
data-sizes="100vw">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=315x0 315w, https://smushcdn.com/img.jpg?size=552x0 552w"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px">
|
||||
</picture>`,
|
||||
'media="(max-width: 799px)"'
|
||||
],
|
||||
[
|
||||
'For non-responsive <source> elements in a <picture>, only appends new CDN srcset entry only to the selected source.',
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-1024x1024.webp" type="image/webp">
|
||||
<source data-srcset="https://smushcdn.com/img.jpg?size=300x300" type="image/jpeg">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-1024x1024.webp" type="image/webp">
|
||||
<source data-srcset="https://smushcdn.com/img.jpg?size=315x0 315w, https://smushcdn.com/img.jpg?size=552x0 552w" type="image/jpeg">
|
||||
<img data-src="https://smushcdn.com/img.jpg" data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=315x0 315w, https://smushcdn.com/img.jpg?size=552x0 552w\" data-original-sizes="(max-width: 1024px) 100vw, 1024px">
|
||||
</picture>`,
|
||||
'type="image/jpeg"',
|
||||
],
|
||||
];
|
||||
|
||||
test.each(
|
||||
autoResizedImagesWithRetina
|
||||
)( 'test resizeImageWithCDN with retina: %s', ( description, imageMarkup, expectedMarkup, selectedSourceString = 'source' ) => {
|
||||
jest.spyOn(instance, 'isSourceActive').mockImplementation((sourceElement) => {
|
||||
const markup = sourceElement.outerHTML;
|
||||
return markup.includes( selectedSourceString );
|
||||
});
|
||||
window.devicePixelRatio = 1.75; // Simulate retina display
|
||||
|
||||
const container = document.createElement( 'div' );
|
||||
container.innerHTML = imageMarkup;
|
||||
const img = container.firstElementChild;
|
||||
document.body.appendChild( container );
|
||||
|
||||
const event = {
|
||||
detail: { width: 315, instance: {}, dataAttr: true },
|
||||
target: container.querySelector('img'),
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
|
||||
instance.maybeAutoResize( event );
|
||||
|
||||
const updatedMarkup = (document.body.querySelector('picture') || document.body.querySelector('img')).outerHTML;
|
||||
|
||||
expect( normalizeHtml( updatedMarkup ) ).toBe( normalizeHtml( expectedMarkup ) );
|
||||
} );
|
||||
|
||||
test( 'resizeImageWithCDN with custom resizing width', () => {
|
||||
const imageMarkup = `<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px">`;
|
||||
const container = document.createElement( 'div' );
|
||||
container.innerHTML = imageMarkup;
|
||||
const img = container.firstElementChild;
|
||||
// Appending the image into document to ensures the event can propagate through the DOM tree,
|
||||
// allowing listeners on document to catch it.
|
||||
document.body.appendChild( img );
|
||||
|
||||
// Listen and prevent default on the custom event.
|
||||
const handler = function( e ) {
|
||||
e.detail.resizeWidth = 400;
|
||||
};
|
||||
document.addEventListener( SMUSH_BEFORE_SIZES, handler );
|
||||
|
||||
// Create a real CustomEvent
|
||||
const event = new CustomEvent( LAZY_BEFORE_SIZES, {
|
||||
detail: { instance: {}, width: 600, dataAttr: true },
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
} );
|
||||
|
||||
Object.defineProperty( event, 'target', { value: img } );
|
||||
|
||||
isSmushLazySizesInstance.mockReturnValue( true );
|
||||
document.dispatchEvent( event );
|
||||
|
||||
const newSrcset = img.getAttribute( 'data-srcset' );
|
||||
const expectedSrcset = 'https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=400x0 400w';
|
||||
expect( newSrcset ).toBe( expectedSrcset );
|
||||
|
||||
document.removeEventListener( SMUSH_BEFORE_SIZES, handler );
|
||||
} );
|
||||
|
||||
const autoResizedPictures = [
|
||||
[
|
||||
'New srcset entry appended to source with webp type',
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.webp 300w, https://smushcdn.com/img.webp 1024w" type="image/webp">
|
||||
<source data-srcset="https://smushcdn.com/img.jpg?size=300x300 300w">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.webp 300w, https://smushcdn.com/img.webp 1024w, https://smushcdn.com/img.webp?size=600x0 600w" type="image/webp">
|
||||
<source data-srcset="https://smushcdn.com/img.jpg?size=300x300 300w, https://smushcdn.com/img.jpg?size=600x0 600w">
|
||||
<img data-src="https://smushcdn.com/img.jpg" data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=600x0 600w" data-original-sizes="(max-width: 1024px) 100vw, 1024px">
|
||||
</picture>`
|
||||
],
|
||||
[
|
||||
'Adds new CDN srcset entry for 600w in <picture> since it is not within precision (5) of existing 594w',
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=594x0 594w">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=594x0 594w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=594x0 594w, https://smushcdn.com/img.jpg?size=600x0 600w">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=594x0 594w, https://smushcdn.com/img.jpg?size=600x0 600w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`
|
||||
],
|
||||
[
|
||||
'Adds new CDN srcset entry for 600w in <picture> since similar source in srcset smaller than requested width',
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w, https://smushcdn.com/img.jpg?size=600x0 600w">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w, https://smushcdn.com/img.jpg?size=600x0 600w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`
|
||||
],
|
||||
[
|
||||
'Adds new CDN srcset entry for 600w in <picture> since similar source in srcset smaller than requested width',
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w, https://smushcdn.com/img.jpg?size=600x0 600w">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=595x0 595w, https://smushcdn.com/img.jpg?size=600x0 600w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`
|
||||
],
|
||||
[
|
||||
'Multi-Breakpoint <Picture> template using srcset and sizes',
|
||||
`<picture>
|
||||
<source media="(min-width: 800px)"
|
||||
data-srcset="https://smushcdn.com/medium-large.jpg 800w, https://smushcdn.com/large.jpg 1024w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source
|
||||
media="(min-width: 500px)"
|
||||
data-srcset="https://smushcdn.com/medium.jpg 500w, https://smushcdn.com/medium-smaller.jpg 400w"
|
||||
data-sizes="(min-width: 500px) 100vw">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source media="(min-width: 800px)"
|
||||
data-srcset="https://smushcdn.com/medium-large.jpg 800w, https://smushcdn.com/large.jpg 1024w, https://smushcdn.com/large.jpg?size=600x0 600w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source
|
||||
media="(min-width: 500px)"
|
||||
data-srcset="https://smushcdn.com/medium.jpg 500w, https://smushcdn.com/medium-smaller.jpg 400w, https://smushcdn.com/medium.jpg?size=600x0 600w"
|
||||
data-sizes="(min-width: 500px) 100vw">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=600x0 600w"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px">
|
||||
</picture>`
|
||||
],
|
||||
[
|
||||
'Responsive <source> with type and media attributes in <picture> element',
|
||||
`<picture>
|
||||
<source media="(min-width: 800px)"
|
||||
type="image/webp"
|
||||
data-srcset="https://smushcdn.com/medium-large.webp 800w, https://smushcdn.com/large.webp 1024w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source media="(min-width: 800px)"
|
||||
type="image/jpeg"
|
||||
data-srcset="https://smushcdn.com/medium-large.jpg 800w, https://smushcdn.com/large.jpg 1024w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source
|
||||
media="(max-width: 799px)"
|
||||
type="image/webp"
|
||||
data-srcset="https://smushcdn.com/medium.webp 500w, https://smushcdn.com/medium-smaller.webp 400w"
|
||||
data-sizes="100vw">
|
||||
<source
|
||||
media="(max-width: 799px)"
|
||||
type="image/jpeg"
|
||||
data-srcset="https://smushcdn.com/medium.jpg 500w, https://smushcdn.com/medium-smaller.jpg 400w"
|
||||
data-sizes="100vw">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source media="(min-width: 800px)"
|
||||
type="image/webp"
|
||||
data-srcset="https://smushcdn.com/medium-large.webp 800w, https://smushcdn.com/large.webp 1024w, https://smushcdn.com/large.webp?size=600x0 600w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source media="(min-width: 800px)"
|
||||
type="image/jpeg"
|
||||
data-srcset="https://smushcdn.com/medium-large.jpg 800w, https://smushcdn.com/large.jpg 1024w, https://smushcdn.com/large.jpg?size=600x0 600w"
|
||||
data-sizes="(min-width: 800px) 100vw">
|
||||
<source
|
||||
media="(max-width: 799px)"
|
||||
type="image/webp"
|
||||
data-srcset="https://smushcdn.com/medium.webp 500w, https://smushcdn.com/medium-smaller.webp 400w, https://smushcdn.com/medium.webp?size=600x0 600w"
|
||||
data-sizes="100vw">
|
||||
<source
|
||||
media="(max-width: 799px)"
|
||||
type="image/jpeg"
|
||||
data-srcset="https://smushcdn.com/medium.jpg 500w, https://smushcdn.com/medium-smaller.jpg 400w, https://smushcdn.com/medium.jpg?size=600x0 600w"
|
||||
data-sizes="100vw">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=600x0 600w"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px">
|
||||
</picture>`
|
||||
],
|
||||
[
|
||||
'Multi-format <source> elements in the <picture> element',
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-1024x1024.webp" type="image/webp">
|
||||
<source data-srcset="https://smushcdn.com/img.jpg?size=300x300" type="image/jpeg">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-1024x1024.webp" type="image/webp">
|
||||
<source data-srcset="https://smushcdn.com/img.jpg?size=600x0" type="image/jpeg">
|
||||
<img data-src="https://smushcdn.com/img.jpg" data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=600x0 600w" data-original-sizes="(max-width: 1024px) 100vw, 1024px">
|
||||
</picture>`,
|
||||
'type="image/jpeg"',
|
||||
],
|
||||
[
|
||||
'<source> elements with descending min-widths in the <picture> element',
|
||||
`<picture>
|
||||
<source media="(min-width: 800px)" data-srcset="https://smushcdn.com/large.jpg">
|
||||
<source media="(min-width: 500px)" data-srcset="https://smushcdn.com/medium.jpg">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source media="(min-width: 800px)" data-srcset="https://smushcdn.com/large.jpg?size=600x0">
|
||||
<source media="(min-width: 500px)" data-srcset="https://smushcdn.com/medium.jpg">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=600x0 600w"
|
||||
data-original-sizes="(max-width: 300px) 100vw, 300px">
|
||||
</picture>`,
|
||||
'media="(min-width: 800px)"',
|
||||
],
|
||||
];
|
||||
|
||||
const skippedPictures = [
|
||||
[
|
||||
'Srcset not changed in <source> elements when x descriptor is found',
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.webp 1x, https://smushcdn.com/img.webp 2x" type="image/webp">
|
||||
<source data-srcset="https://smushcdn.com/img.jpg?size=300x300 1x">
|
||||
<img data-src="https://smushcdn.com/img.jpg"
|
||||
data-srcset="https://smushcdn.com/img-300x300.jpg 300w"
|
||||
data-original-sizes="(max-width: 1024px) 100vw, 1024px" >
|
||||
</picture>`,
|
||||
`<picture>
|
||||
<source data-srcset="https://smushcdn.com/img-300x300.webp 1x, https://smushcdn.com/img.webp 2x" type="image/webp">
|
||||
<source data-srcset="https://smushcdn.com/img.jpg?size=300x300 1x">
|
||||
<img data-src="https://smushcdn.com/img.jpg" data-srcset="https://smushcdn.com/img-300x300.jpg 300w, https://smushcdn.com/img.jpg?size=600x0 600w" data-original-sizes="(max-width: 1024px) 100vw, 1024px">
|
||||
</picture>`
|
||||
]
|
||||
];
|
||||
|
||||
test.each([
|
||||
...skippedPictures,
|
||||
... autoResizedPictures
|
||||
] )( 'test auto resize picture element: %s', ( description, pictureMarkup, expectedMarkup, selectedSourceString = 'source' ) => {
|
||||
jest.spyOn(instance, 'isSourceActive').mockImplementation((sourceElement) => {
|
||||
const markup = sourceElement.outerHTML;
|
||||
return markup.includes( selectedSourceString );
|
||||
});
|
||||
const container = document.createElement( 'div' );
|
||||
container.innerHTML = pictureMarkup;
|
||||
document.body.appendChild( container );
|
||||
|
||||
const event = {
|
||||
detail: { width: 600, instance: {}, dataAttr: true },
|
||||
target: container.querySelector('img'),
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
|
||||
instance.maybeAutoResize( event );
|
||||
const updatedMarkup = document.body.querySelector('picture').outerHTML;
|
||||
|
||||
expect( normalizeHtml( updatedMarkup ) ).toBe( normalizeHtml( expectedMarkup ) );
|
||||
} );
|
||||
|
||||
function normalizeHtml(html) {
|
||||
return html
|
||||
.replace(/\s+/g, ' ') // collapse whitespace
|
||||
.replace(/\s+>/g, '>') // remove space(s) before '>'
|
||||
.trim();
|
||||
}
|
||||
|
||||
test.each(
|
||||
[
|
||||
[
|
||||
// 1
|
||||
'No match: all sources are smaller than required width',
|
||||
[
|
||||
{ value: 300, unit: 'w' },
|
||||
{ value: 600, unit: 'w' },
|
||||
],
|
||||
602,
|
||||
undefined,
|
||||
],
|
||||
[
|
||||
// 2
|
||||
'No match: units do not match',
|
||||
[
|
||||
{ value: 300, unit: 'w' },
|
||||
{ value: 604, unit: 'h' },
|
||||
],
|
||||
602,
|
||||
undefined,
|
||||
],
|
||||
[
|
||||
// 3
|
||||
'Match found: source within precision range of required width',
|
||||
[
|
||||
{ value: 300, unit: 'w' },
|
||||
{ value: 604, unit: 'w' },
|
||||
],
|
||||
602,
|
||||
{ value: 604 },
|
||||
],
|
||||
]
|
||||
)( 'findSimilarSource returns matching source within precision: %s', ( description, sources, resizeWidth, expectedMatch ) => {
|
||||
const unit = 'w';
|
||||
const precision = 5;
|
||||
const found = instance.findSimilarSource( sources, resizeWidth, unit, precision );
|
||||
const expectedResult = expectedMatch ? expect.objectContaining( expectedMatch ) : undefined;
|
||||
expect( found ).toEqual( expectedResult );
|
||||
} );
|
||||
|
||||
test( 'getElementWidth returns correctly.', () => {
|
||||
const img = document.createElement( 'img' );
|
||||
// Mock getComputedStyle to return a non-numeric width
|
||||
window.getComputedStyle = () => ( { width: 'auto' } );
|
||||
expect( instance.getElementWidth( img ) ).toBe( 'auto' );
|
||||
window.getComputedStyle = () => ( { width: '100px' } );
|
||||
expect( instance.getElementWidth( img ) ).toBe( 100 );
|
||||
} );
|
||||
|
||||
test( 'parseSrcSet sorts sources in descending order', () => {
|
||||
const srcset = 'img-200.jpg 200w, img-400.jpg 400w, img-300.jpg 300w';
|
||||
const sources = instance.parseSrcSet( srcset );
|
||||
expect( sources[ 0 ].value ).toBe( 400 );
|
||||
expect( sources[ 1 ].value ).toBe( 300 );
|
||||
expect( sources[ 2 ].value ).toBe( 200 );
|
||||
} );
|
||||
|
||||
test( 'parseSrcSet parses retina descriptors correctly', () => {
|
||||
const srcset = 'img-1x.jpg 1x, img-2x.jpg 2x, img-400.jpg 400w';
|
||||
const sources = instance.parseSrcSet( srcset );
|
||||
|
||||
// Should parse the 'x' descriptors as floats and unit as 'x'
|
||||
expect( sources.find( ( s ) => s.unit === 'x' && s.value === 2 ).src ).toBe( 'img-2x.jpg' );
|
||||
expect( sources.find( ( s ) => s.unit === 'x' && s.value === 1 ).src ).toBe( 'img-1x.jpg' );
|
||||
// Should also parse the 'w' descriptor
|
||||
expect( sources.find( ( s ) => s.unit === 'w' && s.value === 400 ).src ).toBe( 'img-400.jpg' );
|
||||
} );
|
||||
|
||||
test.each( [
|
||||
[
|
||||
'Not a thumbnail',
|
||||
'https://example.com/image.jpg',
|
||||
[
|
||||
{ value: 500, src: 'https://example.com/image-500x500.jpg' },
|
||||
{ value: 300, src: 'https://example.com/image-300x300.jpg' },
|
||||
{ value: 200, src: 'https://example.com/image-200x200.jpg' },
|
||||
],
|
||||
400,
|
||||
'https://example.com/image.jpg',
|
||||
],
|
||||
[
|
||||
'Is a thumbnail but no larger source',
|
||||
'https://example.com/image.jpg',
|
||||
[
|
||||
{ value: 300, src: 'https://example.com/image-300x300.jpg' },
|
||||
{ value: 200, src: 'https://example.com/image-200x200.jpg' },
|
||||
],
|
||||
400,
|
||||
'https://example.com/image.jpg',
|
||||
],
|
||||
[
|
||||
'Is a thumbnail and larger source exists',
|
||||
'https://example.com/image-400x400.jpg',
|
||||
[
|
||||
{ value: 500, src: 'https://example.com/image-500x500.jpg' },
|
||||
{ value: 300, src: 'https://example.com/image-300x300.jpg' },
|
||||
{ value: 200, src: 'https://example.com/image-200x200.jpg' },
|
||||
],
|
||||
400,
|
||||
'https://example.com/image-500x500.jpg',
|
||||
]
|
||||
] )(
|
||||
'getBaseImageSrcForResize %s',
|
||||
( desc, src, sortedSources, resizeWidth, expected ) => {
|
||||
expect( instance.getBaseImageSrcForResize( src, sortedSources, resizeWidth ) ).toBe( expected );
|
||||
}
|
||||
);
|
||||
|
||||
test( 'updateElementSrcset sets attribute only if changed', () => {
|
||||
const img = document.createElement( 'img' );
|
||||
const originalSrcset = 'img-300.jpg 300w, img-600.jpg 600w';
|
||||
const newSrcset = 'img-300.jpg 300w, img-600.jpg 600w, img-900.jpg 900w';
|
||||
|
||||
// Should set attribute because newSrcset !== originalSrcset
|
||||
instance.updateElementSrcset( img, originalSrcset, newSrcset );
|
||||
expect( img.getAttribute( 'data-srcset' ) ).toBe( newSrcset );
|
||||
|
||||
// Should NOT set attribute because newSrcset === originalSrcset
|
||||
img.removeAttribute( 'data-srcset' );
|
||||
instance.updateElementSrcset( img, originalSrcset, originalSrcset );
|
||||
expect( img.getAttribute( 'data-srcset' ) ).toBe( null );
|
||||
} );
|
||||
|
||||
test( 'parseSrcSet handles sources with equal values and sorts descending', () => {
|
||||
const srcset = 'img-200.jpg 200w, img-400.jpg 400w, img-400b.jpg 400w, img-300.jpg 300w';
|
||||
const sources = instance.parseSrcSet( srcset );
|
||||
|
||||
// Should be sorted descending, and equal values retain their relative order
|
||||
expect( sources[ 0 ].value ).toBe( 400 );
|
||||
expect( sources[ 1 ].value ).toBe( 400 );
|
||||
expect( sources[ 2 ].value ).toBe( 300 );
|
||||
expect( sources[ 3 ].value ).toBe( 200 );
|
||||
|
||||
// The two 400w sources should both be present and in the order they appeared in the srcset
|
||||
expect( sources[ 0 ].src ).toBe( 'img-400.jpg' );
|
||||
expect( sources[ 1 ].src ).toBe( 'img-400b.jpg' );
|
||||
} );
|
||||
} );
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
import { describe, expect, test, it } from '@jest/globals';
|
||||
import { LCPDetector, SmushLCPDetector } from '../frontend/detector';
|
||||
|
||||
describe( 'background data from property value', () => {
|
||||
const dataSet = [
|
||||
[
|
||||
// background-image: url
|
||||
"url('http://localhost/wp-content/uploads/2024/08/image1.jpeg')",
|
||||
'background-image',
|
||||
[
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg'
|
||||
]
|
||||
],
|
||||
[
|
||||
// background-image: relative url
|
||||
"url('/wp-content/uploads/2024/08/image1.jpeg')",
|
||||
'background-image',
|
||||
[
|
||||
'/wp-content/uploads/2024/08/image1.jpeg'
|
||||
]
|
||||
],
|
||||
[
|
||||
// background-image: image-set
|
||||
'image-set(' +
|
||||
"'http://localhost/wp-content/uploads/2024/08/image1-768x437.jpeg' 1x, " +
|
||||
"'http://localhost/wp-content/uploads/2024/08/image1.jpeg' 2x" +
|
||||
');',
|
||||
'background-image-set',
|
||||
[
|
||||
'http://localhost/wp-content/uploads/2024/08/image1-768x437.jpeg',
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg',
|
||||
]
|
||||
],
|
||||
[
|
||||
// background-image: image-set with relative URL.
|
||||
'image-set(' +
|
||||
"'/wp-content/uploads/2024/08/image1-768x437.jpeg' 1x, " +
|
||||
"'/wp-content/uploads/2024/08/image1.jpeg' 2x" +
|
||||
');',
|
||||
'background-image-set',
|
||||
[
|
||||
'/wp-content/uploads/2024/08/image1-768x437.jpeg',
|
||||
'/wp-content/uploads/2024/08/image1.jpeg',
|
||||
]
|
||||
],
|
||||
[
|
||||
// background-image: image-set with url
|
||||
'image-set(' +
|
||||
"url('http://localhost/wp-content/uploads/2024/08/image1-768x437.jpeg') 1x, " +
|
||||
"url('http://localhost/wp-content/uploads/2024/08/image1.jpeg') 2x" +
|
||||
');',
|
||||
'background-image-set',
|
||||
[
|
||||
'http://localhost/wp-content/uploads/2024/08/image1-768x437.jpeg',
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg',
|
||||
]
|
||||
],
|
||||
[
|
||||
// background-image: image-set with url and relative URL
|
||||
'image-set(' +
|
||||
"url('/wp-content/uploads/2024/08/image1-768x437.jpeg') 1x, " +
|
||||
"url('/wp-content/uploads/2024/08/image1.jpeg') 2x" +
|
||||
');',
|
||||
'background-image-set',
|
||||
[
|
||||
'/wp-content/uploads/2024/08/image1-768x437.jpeg',
|
||||
'/wp-content/uploads/2024/08/image1.jpeg',
|
||||
]
|
||||
],
|
||||
[
|
||||
// background-image: image-set query params
|
||||
'image-set(' +
|
||||
'http://localhost/wp-content/uploads/2024/08/image1-768x437.jpeg?hello=world 1x, ' +
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg?yellow=world 2x' +
|
||||
');',
|
||||
'background-image-set',
|
||||
[
|
||||
'http://localhost/wp-content/uploads/2024/08/image1-768x437.jpeg?hello=world',
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg?yellow=world',
|
||||
]
|
||||
],
|
||||
[
|
||||
// background-image: image-set query params with relative URL
|
||||
'image-set(' +
|
||||
'/wp-content/uploads/2024/08/image1-768x437.jpeg?hello=world 1x, ' +
|
||||
'/wp-content/uploads/2024/08/image1.jpeg?yellow=world 2x' +
|
||||
');',
|
||||
'background-image-set',
|
||||
[
|
||||
'/wp-content/uploads/2024/08/image1-768x437.jpeg?hello=world',
|
||||
'/wp-content/uploads/2024/08/image1.jpeg?yellow=world',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
it.each( dataSet )( 'returns correct data for given property value', ( propertyValue, type, urls ) => {
|
||||
const lcpDetector = new SmushLCPDetector();
|
||||
lcpDetector.shouldUseRelativeImageURL = ! propertyValue.includes( 'http://localhost' );
|
||||
const backgroundDataForElement = lcpDetector.getBackgroundDataForPropertyValue( propertyValue );
|
||||
|
||||
expect( backgroundDataForElement ).toStrictEqual( {
|
||||
type,
|
||||
urls,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'shouldUseRelativeImageURL', () => {
|
||||
const dataSet = [
|
||||
[
|
||||
// Case: Element contains absolute URL in src attribute
|
||||
'<img src="http://localhost/wp-content/uploads/2024/08/image1.jpeg">',
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg',
|
||||
false,
|
||||
],
|
||||
[
|
||||
// Case: Element contains relative URL in src attribute
|
||||
'<img src="/wp-content/uploads/2024/08/image1.jpeg">',
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg',
|
||||
true,
|
||||
],
|
||||
[
|
||||
// Case: Element does not contain the URL
|
||||
'<img src="/wp-content/uploads/2024/08/other-image.jpeg">',
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg',
|
||||
false,
|
||||
],
|
||||
[
|
||||
// Case: Element contains absolute URL in background style
|
||||
'<div style="background-image: url(http://localhost/wp-content/uploads/2024/08/image1.jpeg);"></div>',
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg',
|
||||
false,
|
||||
],
|
||||
[
|
||||
// Case: Element contains relative URL in background style
|
||||
'<div style="background-image: url(/wp-content/uploads/2024/08/image1.jpeg);"></div>',
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg',
|
||||
true,
|
||||
],
|
||||
[
|
||||
// Case: Element is null
|
||||
null,
|
||||
'http://localhost/wp-content/uploads/2024/08/image1.jpeg',
|
||||
false,
|
||||
],
|
||||
];
|
||||
|
||||
it.each( dataSet )(
|
||||
'returns %s when element is %s and absoluteImageUrl is %s',
|
||||
( elementHTML, absoluteImageUrl, expected ) => {
|
||||
const lcpDetector = new SmushLCPDetector();
|
||||
const element = elementHTML
|
||||
? document.createElement( 'div' )
|
||||
: null;
|
||||
|
||||
if ( element ) {
|
||||
element.innerHTML = elementHTML;
|
||||
}
|
||||
|
||||
const result = lcpDetector.shouldUseRelativeImageURL(
|
||||
element?.firstChild || element,
|
||||
absoluteImageUrl
|
||||
);
|
||||
|
||||
expect( result ).toBe( expected );
|
||||
}
|
||||
);
|
||||
} );
|
||||
@@ -0,0 +1,59 @@
|
||||
import '../scss/app.scss';
|
||||
|
||||
/**
|
||||
* Admin modules
|
||||
*/
|
||||
|
||||
const WP_Smush = WP_Smush || {};
|
||||
window.WP_Smush = WP_Smush;
|
||||
|
||||
/**
|
||||
* IE polyfill for includes.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param {string} search
|
||||
* @param {number} start
|
||||
* @return {boolean} Returns true if searchString appears as a substring of the result of converting this
|
||||
* object to a String, at one or more positions that are
|
||||
* greater than or equal to position; otherwise, returns false.
|
||||
*/
|
||||
if ( ! String.prototype.includes ) {
|
||||
String.prototype.includes = function( search, start ) {
|
||||
if ( typeof start !== 'number' ) {
|
||||
start = 0;
|
||||
}
|
||||
|
||||
if ( start + search.length > this.length ) {
|
||||
return false;
|
||||
}
|
||||
return this.indexOf( search, start ) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
require( './modules/helpers' );
|
||||
require( './modules/admin' );
|
||||
require( './modules/admin-common' );
|
||||
require( './modules/bulk-smush' );
|
||||
require( './common/media-library-scanner' );
|
||||
require( './modules/media-library-scanner-on-bulk-smush' );
|
||||
require( './modules/media-library-scanner-on-dashboard' );
|
||||
require( './modules/onboarding' );
|
||||
require( './modules/onboarding-free' );
|
||||
require( './modules/directory-smush' );
|
||||
require( './smush/lazy-load' );
|
||||
require( './modules/bulk-restore' );
|
||||
require( './smush/settings' );
|
||||
require( './smush/product-analytics' );
|
||||
|
||||
/**
|
||||
* Notice scripts.
|
||||
*
|
||||
* Notices are used in the following functions:
|
||||
*
|
||||
* @used-by \Smush\Core\Modules\Smush::smush_updated()
|
||||
* @used-by \Smush\Core\Integrations\S3::3_support_required_notice()
|
||||
* @used-by \Smush\App\Abstract_Page::installation_notice()
|
||||
*
|
||||
* TODO: should this be moved out in a separate file like common.scss?
|
||||
*/
|
||||
require( './modules/notice' );
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
/* global WP_Smush */
|
||||
export const UpsellManger = ( () => {
|
||||
return {
|
||||
maybeShowCDNActivationNotice() {
|
||||
if ( ! wp_smush_msgs.smush_cdn_activation_notice ) {
|
||||
return;
|
||||
}
|
||||
WP_Smush.helpers.renderActivationCDNNotice( wp_smush_msgs.smush_cdn_activation_notice );
|
||||
},
|
||||
maybeShowCDNUpsellForPreSiteOnStart() {
|
||||
const upsellCdn = document.querySelector( '.wp-smush-upsell-cdn' );
|
||||
if ( upsellCdn ) {
|
||||
upsellCdn.classList.remove( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
maybeShowCDNUpsellForPreSiteOnCompleted() {
|
||||
const upsellCdn = document.querySelector( '.wp-smush-upsell-cdn' );
|
||||
if ( upsellCdn ) {
|
||||
upsellCdn.classList.remove( 'sui-hidden' );
|
||||
}
|
||||
}
|
||||
};
|
||||
} )();
|
||||
export const GlobalStats = ( () => {
|
||||
const $ = document.querySelector.bind( document );
|
||||
const summarySmush = $( '.sui-summary-smush-metabox' );
|
||||
if ( ! summarySmush ) {
|
||||
return {};
|
||||
}
|
||||
// Cache initial stats.
|
||||
let boStats = window.wp_smushit_data.bo_stats;
|
||||
let globalStats = {
|
||||
count_images: 0,
|
||||
count_total: 0,
|
||||
count_resize: 0,
|
||||
count_skipped: 0,
|
||||
count_smushed: 0,
|
||||
savings_bytes: 0,
|
||||
savings_resize: 0,
|
||||
size_after: 0,
|
||||
size_before: 0,
|
||||
savings_percent: 0,
|
||||
percent_grade: 'sui-grade-dismissed',
|
||||
percent_metric: 0,
|
||||
percent_optimized: 0,
|
||||
remaining_count: 0,
|
||||
human_bytes: '',
|
||||
savings_conversion_human: '',
|
||||
savings_conversion: 0,
|
||||
};
|
||||
|
||||
const imageScore = $( '#smush-image-score' );
|
||||
const logBulk = $( '.smush-final-log .smush-bulk-errors' );
|
||||
const bulkSmushCountContent = $( '#wp-smush-bulk-content' );
|
||||
let allErrors = {};
|
||||
|
||||
const generateGlobalStatsFromSmushData = ( smushScriptData ) => {
|
||||
window.wp_smushit_data = Object.assign( window.wp_smushit_data, smushScriptData || {} );
|
||||
globalStats = Object.keys( globalStats ).reduce( function( newStats, key ) {
|
||||
if ( key in window.wp_smushit_data ) {
|
||||
newStats[ key ] = window.wp_smushit_data[ key ];
|
||||
}
|
||||
return newStats;
|
||||
}, {} );
|
||||
}
|
||||
|
||||
generateGlobalStatsFromSmushData( window.wp_smushit_data );
|
||||
|
||||
return {
|
||||
isChangedStats( newBoStats ) {
|
||||
const primaryKeys = [ 'total_items', 'processed_items', 'failed_items', 'is_cancelled', 'is_completed', 'is_dead' ];
|
||||
return primaryKeys.some( ( key ) => {
|
||||
return newBoStats[ key ] !== boStats[ key ];
|
||||
} );
|
||||
},
|
||||
setBoStats( newBoStats ) {
|
||||
boStats = Object.assign( boStats, newBoStats || {} );
|
||||
return this;
|
||||
},
|
||||
getBoStats() {
|
||||
return boStats;
|
||||
},
|
||||
setGlobalStats( newGlobalStats ) {
|
||||
globalStats = Object.assign( globalStats, newGlobalStats || {} );
|
||||
return this;
|
||||
},
|
||||
getGlobalStats() {
|
||||
return globalStats;
|
||||
},
|
||||
/**
|
||||
* Circle progress bar.
|
||||
*/
|
||||
renderScoreProgress() {
|
||||
imageScore.className = imageScore.className.replace( /(^|\s)sui-grade-\S+/g, '' );
|
||||
imageScore.classList.add( globalStats.percent_grade );
|
||||
imageScore.dataset.score = globalStats.percent_optimized;
|
||||
imageScore.querySelector( '.sui-circle-score-label' ).innerHTML = globalStats.percent_optimized;
|
||||
|
||||
imageScore
|
||||
.querySelector( 'circle:last-child' )
|
||||
.setAttribute( 'style', '--metric-array:' + ( 2.63893782902 * globalStats.percent_metric ) + ' ' + ( 263.893782902 - globalStats.percent_metric ) );
|
||||
},
|
||||
/**
|
||||
* Summary detail - center meta box.
|
||||
*/
|
||||
renderSummaryDetail() {
|
||||
this.renderTotalStats();
|
||||
this.renderResizedStats();
|
||||
this.renderConversionSavings();
|
||||
},
|
||||
renderTotalStats() {
|
||||
// Total savings.
|
||||
summarySmush.querySelector( '.sui-summary-large.wp-smush-stats-human' ).innerHTML = globalStats.human_bytes;
|
||||
// Update the savings percent.
|
||||
summarySmush.querySelector( '.wp-smush-savings .wp-smush-stats-percent' ).innerHTML = globalStats.savings_percent;
|
||||
// To total smushed images files.
|
||||
summarySmush.querySelector( '.wp-smush-count-total .wp-smush-total-optimised' ).innerHTML = globalStats.count_images;
|
||||
},
|
||||
renderResizedStats() {
|
||||
const resizeCountElement = summarySmush.querySelector( '.wp-smush-count-resize-total' );
|
||||
if ( ! resizeCountElement ) {
|
||||
return;
|
||||
}
|
||||
if ( globalStats.count_resize > 0 ) {
|
||||
resizeCountElement.classList.remove( 'sui-hidden' );
|
||||
} else {
|
||||
resizeCountElement.classList.add( 'sui-hidden' );
|
||||
}
|
||||
resizeCountElement.querySelector( '.wp-smush-total-optimised' ).innerHTML = globalStats.count_resize;
|
||||
},
|
||||
renderConversionSavings() {
|
||||
// PNG2JPG Savings.
|
||||
const conversionSavingsElement = summarySmush.querySelector( '.smush-conversion-savings .wp-smush-stats' );
|
||||
if ( ! conversionSavingsElement ) {
|
||||
return;
|
||||
}
|
||||
conversionSavingsElement.innerHTML = globalStats.savings_conversion_human;
|
||||
if ( globalStats.savings_conversion > 0 ) {
|
||||
conversionSavingsElement.parentElement.classList.remove( 'sui-hidden' );
|
||||
} else {
|
||||
conversionSavingsElement.parentElement.classList.add( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
renderBoxSummary() {
|
||||
// Circle core progress.
|
||||
this.renderScoreProgress();
|
||||
// Summary detail.
|
||||
this.renderSummaryDetail();
|
||||
},
|
||||
setErrors( newErrors ) {
|
||||
allErrors = newErrors || {};
|
||||
},
|
||||
getErrors() {
|
||||
return allErrors;
|
||||
},
|
||||
renderErrors() {
|
||||
if ( ! Object.keys( allErrors ).length || ! boStats.is_completed ) {
|
||||
return;
|
||||
}
|
||||
const errors = [];
|
||||
const errorKeys = Object.keys( allErrors );
|
||||
// Cache error code to avoid double upsell notice.
|
||||
let showAnimatedUpsell = false;
|
||||
errorKeys.map( ( image_id, index ) => {
|
||||
const upsellErrorCode = allErrors[ image_id ].error_code;
|
||||
if ( index < 5 && 'animated' === upsellErrorCode ) {
|
||||
showAnimatedUpsell = true;
|
||||
}
|
||||
errors.push( WP_Smush.helpers.prepareBulkSmushErrorRow(
|
||||
allErrors[ image_id ].error_message,
|
||||
allErrors[ image_id ].file_name,
|
||||
allErrors[ image_id ].thumbnail,
|
||||
image_id,
|
||||
'media',
|
||||
allErrors[ image_id ].error_code,
|
||||
) );
|
||||
}
|
||||
);
|
||||
logBulk.innerHTML = errors.join( '' );
|
||||
logBulk.parentElement.classList.remove( 'sui-hidden' );
|
||||
logBulk.parentElement.style.display = null;
|
||||
// Show view all.
|
||||
if ( errorKeys.length > 1 ) {
|
||||
$( '.smush-bulk-errors-actions' ).classList.remove( 'sui-hidden' );
|
||||
}
|
||||
|
||||
// Show animated upsell CDN if user disabled CDN and found an animated error in first 5 errors.
|
||||
if ( showAnimatedUpsell ) {
|
||||
UpsellManger.maybeShowCDNActivationNotice();
|
||||
}
|
||||
},
|
||||
resetAndHideBulkErrors() {
|
||||
if ( ! logBulk ) {
|
||||
return;
|
||||
}
|
||||
this.resetErrors();
|
||||
logBulk.parentElement.classList.add( 'sui-hidden' );
|
||||
logBulk.innerHTML = '';
|
||||
},
|
||||
resetErrors() {
|
||||
allErrors = {};
|
||||
},
|
||||
renderStats() {
|
||||
// Render Smush box summary.
|
||||
this.renderBoxSummary();
|
||||
// Render Errors.
|
||||
this.renderErrors();
|
||||
},
|
||||
maybeUpdateBulkSmushCountContent( newContent ) {
|
||||
if ( newContent && bulkSmushCountContent ) {
|
||||
bulkSmushCountContent.innerHTML = newContent;
|
||||
}
|
||||
},
|
||||
updateGlobalStatsFromSmushScriptData( smushScriptData ) {
|
||||
this.maybeUpdateBulkSmushCountContent( smushScriptData?.content );
|
||||
generateGlobalStatsFromSmushData( smushScriptData );
|
||||
return this;
|
||||
},
|
||||
};
|
||||
} )();
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/* global WP_Smush */
|
||||
|
||||
/**
|
||||
* Abstract Media Library Scanner.
|
||||
*
|
||||
*/
|
||||
import Fetcher from '../utils/fetcher';
|
||||
import { scanProgressBar } from './progressbar';
|
||||
import { GlobalStats } from './globalStats';
|
||||
import loopbackTester from '../loopback-tester';
|
||||
const { __ } = wp.i18n;
|
||||
export default class MediaLibraryScanner {
|
||||
constructor() {
|
||||
this.autoSyncDuration = 1500;
|
||||
this.progressTimeoutId = 0;
|
||||
this.scanProgress = scanProgressBar( this.autoSyncDuration );
|
||||
}
|
||||
|
||||
startScan( optimizeOnScanCompleted = false ) {
|
||||
this.onStart();
|
||||
const processType = optimizeOnScanCompleted ? 'smush' : 'scan';
|
||||
loopbackTester.performTest().then( ( res ) => {
|
||||
const isLoopbackHealthy = res?.loopback;
|
||||
if ( isLoopbackHealthy ) {
|
||||
Fetcher.scanMediaLibrary.start( optimizeOnScanCompleted ).then( ( response ) => {
|
||||
if ( ! response?.success ) {
|
||||
this.showFailureNotice( response );
|
||||
this.onStartFailure( response );
|
||||
return;
|
||||
}
|
||||
this.showProgressBar().autoSyncStatus();
|
||||
} );
|
||||
} else {
|
||||
this.showLoopbackErrorModal( processType );
|
||||
this.onStartFailure( res );
|
||||
}
|
||||
} ).catch( ( error ) => {
|
||||
console.error( 'Error:', error );
|
||||
this.showLoopbackErrorModal( processType );
|
||||
this.onStartFailure( error );
|
||||
} );
|
||||
}
|
||||
|
||||
onStart() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
onStartFailure( response ) {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
showFailureNotice( response ) {
|
||||
WP_Smush.helpers.showNotice( response, {
|
||||
showdismiss: true,
|
||||
autoclose: false,
|
||||
} );
|
||||
}
|
||||
|
||||
showLoopbackErrorModal( processType ) {
|
||||
const loopbackErrorModal = document.getElementById( 'smush-loopback-error-dialog' );
|
||||
if ( ! loopbackErrorModal || ! window.SUI ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache current process type.
|
||||
loopbackErrorModal.dataset.processType = processType || 'scan';
|
||||
|
||||
WP_Smush.helpers.showModal( loopbackErrorModal.id );
|
||||
}
|
||||
|
||||
showProgressBar() {
|
||||
this.onShowProgressBar();
|
||||
this.scanProgress.reset().setOnCancelCallback( this.showStopScanningModal.bind( this ) ).open();
|
||||
return this;
|
||||
}
|
||||
|
||||
onShowProgressBar() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
showStopScanningModal() {
|
||||
if ( ! window.SUI ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onShowStopScanningModal();
|
||||
|
||||
window.SUI.openModal(
|
||||
'smush-stop-scanning-dialog',
|
||||
'wpbody-content',
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
onShowStopScanningModal() {
|
||||
this.registerCancelProcessEvent();
|
||||
}
|
||||
|
||||
registerCancelProcessEvent() {
|
||||
const stopScanButton = document.querySelector( '.smush-stop-scanning-dialog-button' );
|
||||
if ( ! stopScanButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopScanButton.addEventListener( 'click', this.cancelProgress.bind( this ), { once: true } );
|
||||
}
|
||||
|
||||
closeStopScanningModal() {
|
||||
if ( ! window.SUI ) {
|
||||
return;
|
||||
}
|
||||
const stopScanningElement = document.querySelector( '#smush-stop-scanning-dialog' );
|
||||
const isModalClosed = ( ! stopScanningElement ) || ! stopScanningElement.classList.contains( 'sui-content-fade-in' );
|
||||
if ( isModalClosed ) {
|
||||
return;
|
||||
}
|
||||
window.SUI.closeModal( 'smush-stop-scanning-dialog' );
|
||||
}
|
||||
|
||||
closeProgressBar() {
|
||||
this.onCloseProgressBar();
|
||||
this.scanProgress.close();
|
||||
}
|
||||
|
||||
onCloseProgressBar() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
updateProgress( stats ) {
|
||||
const totalItems = this.getTotalItems( stats );
|
||||
const processedItems = this.getProcessedItems( stats );
|
||||
|
||||
return this.scanProgress.update( processedItems, totalItems );
|
||||
}
|
||||
|
||||
getProcessedItems( stats ) {
|
||||
return stats?.processed_items || 0;
|
||||
}
|
||||
|
||||
getTotalItems( stats ) {
|
||||
return stats?.total_items || 0;
|
||||
}
|
||||
|
||||
cancelProgress() {
|
||||
this.scanProgress.setCancelButtonOnCancelling();
|
||||
return Fetcher.scanMediaLibrary.cancel().then( ( response ) => {
|
||||
if ( ! response?.success ) {
|
||||
this.onCancelFailure( response );
|
||||
return;
|
||||
}
|
||||
this.onCancelled( response.data );
|
||||
} );
|
||||
}
|
||||
|
||||
onCancelFailure( response ) {
|
||||
WP_Smush.helpers.showNotice( response, {
|
||||
showdismiss: true,
|
||||
autoclose: false,
|
||||
} );
|
||||
this.scanProgress.resetCancelButtonOnFailure();
|
||||
}
|
||||
|
||||
onDead( stats ) {
|
||||
this.clearProgressTimeout();
|
||||
this.closeProgressBar();
|
||||
this.closeStopScanningModal();
|
||||
this.showRetryScanModal();
|
||||
}
|
||||
|
||||
showRetryScanModal() {
|
||||
const retryScanModalElement = document.getElementById( 'smush-retry-scan-notice' );
|
||||
if ( ! window.SUI || ! retryScanModalElement ) {
|
||||
return;
|
||||
}
|
||||
|
||||
retryScanModalElement.querySelector( '.smush-retry-scan-notice-button' ).addEventListener( 'click', ( e ) => {
|
||||
window.SUI.closeModal( 'smush-retry-scan-notice' );
|
||||
const recheckImagesBtn = document.querySelector( '.wp-smush-scan' );
|
||||
if ( ! recheckImagesBtn ) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
recheckImagesBtn.click();
|
||||
}, { once: true } );
|
||||
|
||||
window.SUI.openModal(
|
||||
'smush-retry-scan-notice',
|
||||
'wpbody-content',
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
onCompleted( stats ) {
|
||||
this.onFinish( stats );
|
||||
}
|
||||
|
||||
onCancelled( stats ) {
|
||||
this.onFinish( stats );
|
||||
}
|
||||
|
||||
onFinish( stats ) {
|
||||
this.clearProgressTimeout();
|
||||
const globalStats = stats?.global_stats;
|
||||
this.updateGlobalStatsAndBulkContent( globalStats );
|
||||
this.closeProgressBar();
|
||||
this.closeStopScanningModal();
|
||||
}
|
||||
|
||||
clearProgressTimeout() {
|
||||
if ( this.progressTimeoutId ) {
|
||||
clearTimeout( this.progressTimeoutId );
|
||||
}
|
||||
}
|
||||
|
||||
updateGlobalStatsAndBulkContent( globalStats ) {
|
||||
if ( ! globalStats ) {
|
||||
return;
|
||||
}
|
||||
GlobalStats.updateGlobalStatsFromSmushScriptData( globalStats );
|
||||
GlobalStats.renderStats();
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return Fetcher.scanMediaLibrary.getScanStatus();
|
||||
}
|
||||
|
||||
autoSyncStatus() {
|
||||
const startTime = new Date().getTime();
|
||||
this.getStatus().then( ( response ) => {
|
||||
if ( ! response?.success ) {
|
||||
return;
|
||||
}
|
||||
const stats = response.data;
|
||||
|
||||
if ( stats.is_dead ) {
|
||||
this.onDead( response.data );
|
||||
return;
|
||||
}
|
||||
|
||||
this.beforeUpdateStatus( stats );
|
||||
|
||||
this.updateProgress( stats ).then( () => {
|
||||
this.scanProgress.increaseDurationToHaveChangeOnProgress( new Date().getTime() - startTime );
|
||||
|
||||
const isCompleted = stats?.is_completed;
|
||||
if ( isCompleted ) {
|
||||
this.onCompleted( stats );
|
||||
return;
|
||||
}
|
||||
const isCancelled = stats?.is_cancelled;
|
||||
if ( isCancelled ) {
|
||||
this.onCancelled( stats );
|
||||
return;
|
||||
}
|
||||
|
||||
this.progressTimeoutId = setTimeout( () => this.autoSyncStatus(), this.autoSyncDuration );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
beforeUpdateStatus() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
setInnerText( element, newText ) {
|
||||
if ( ! element ) {
|
||||
return;
|
||||
}
|
||||
element.dataset.originalText = element.dataset.originalText || element.innerText.trim();
|
||||
element.innerText = newText;
|
||||
}
|
||||
|
||||
revertInnerText( element ) {
|
||||
if ( ! element || ! element.dataset.originalText ) {
|
||||
return;
|
||||
}
|
||||
element.innerText = element.dataset.originalText.trim();
|
||||
}
|
||||
|
||||
hideAnElement( element ) {
|
||||
if ( element ) {
|
||||
element.classList.add( 'sui-hidden' );
|
||||
}
|
||||
}
|
||||
|
||||
showAnElement( element ) {
|
||||
if ( element ) {
|
||||
element.classList.remove( 'sui-hidden' );
|
||||
}
|
||||
}
|
||||
}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
/* global WP_Smush */
|
||||
|
||||
/**
|
||||
* SmushProgressBar
|
||||
* TODO: Update progressbar for free version.
|
||||
*
|
||||
* @param autoSyncDuration
|
||||
*/
|
||||
export const scanProgressBar = ( autoSyncDuration ) => {
|
||||
const { __, _n } = wp.i18n;
|
||||
const scanProgressBar = document.querySelector( '.wp-smush-scan-progress-bar-wrapper' );
|
||||
const percentElement = scanProgressBar.querySelector( '.wp-smush-progress-percent' );
|
||||
const progressElement = scanProgressBar.querySelector( '.wp-smush-progress-inner' );
|
||||
const remainingTimeElement = scanProgressBar.querySelector( '.wp-smush-remaining-time' );
|
||||
const cancelBtn = scanProgressBar.querySelector( '.wp-smush-cancel-scan-progress-btn' );
|
||||
const holdOnNoticeElement = scanProgressBar.querySelector( '.wp-smush-scan-hold-on-notice' );
|
||||
let onCancelCallback = () => {};
|
||||
let intervalProgressAnimation = 0;
|
||||
// It should be smaller than autoSyncDuration.
|
||||
const progressTransitionDuration = autoSyncDuration - 300;//1200
|
||||
scanProgressBar.style.setProperty( '--progress-transition-duration', progressTransitionDuration / 1000 + 's' );
|
||||
|
||||
let prevProcessedItems = window.wp_smushit_data?.media_library_scan?.processed_items || 0;
|
||||
const cacheProcessTimePerItem = [];
|
||||
let durationToHaveChangeOnProgress = autoSyncDuration;
|
||||
let timeLimitToShowNotice = 60000;// 60s.
|
||||
return {
|
||||
update( processedItems, totalItems ) {
|
||||
this.updateRemainingTime( processedItems, totalItems );
|
||||
|
||||
let width = ( totalItems && Math.floor( processedItems / totalItems * 100 ) ) || 0;
|
||||
width = Math.min( width, 100 );
|
||||
|
||||
let currentWidth = progressElement.style.width;
|
||||
currentWidth = ( currentWidth && currentWidth.replace( '%', '' ) ) || 0;
|
||||
progressElement.style.width = width + '%';
|
||||
|
||||
return this.animateProgressBar( currentWidth, width );
|
||||
},
|
||||
animateProgressBar( currentWidth, width ) {
|
||||
if ( intervalProgressAnimation ) {
|
||||
clearInterval( intervalProgressAnimation );
|
||||
}
|
||||
return new Promise( ( resolve ) => {
|
||||
const delayTime = progressTransitionDuration / ( width - currentWidth );
|
||||
intervalProgressAnimation = setInterval( () => {
|
||||
// Progress bar label.
|
||||
percentElement.innerHTML = currentWidth + '%';
|
||||
currentWidth++;
|
||||
if ( currentWidth > width ) {
|
||||
resolve();
|
||||
clearInterval( intervalProgressAnimation );
|
||||
}
|
||||
}, delayTime );
|
||||
} );
|
||||
},
|
||||
updateRemainingTime( processedItems, totalItems ) {
|
||||
if ( ! remainingTimeElement ) {
|
||||
return;
|
||||
}
|
||||
const processTimePerItem = this.calcProcessTimePerItem( processedItems ) || 500;
|
||||
const remainingTime = processTimePerItem * ( totalItems - processedItems );
|
||||
remainingTimeElement.innerText = this.formatTime( remainingTime );
|
||||
},
|
||||
calcProcessTimePerItem( processedItems ) {
|
||||
if ( ! processedItems ) {
|
||||
return;
|
||||
}
|
||||
prevProcessedItems = prevProcessedItems <= processedItems ? prevProcessedItems : 0;
|
||||
if ( prevProcessedItems != processedItems ) {
|
||||
const processTimePerItem = Math.floor( durationToHaveChangeOnProgress / ( processedItems - prevProcessedItems ) );
|
||||
|
||||
prevProcessedItems = processedItems;
|
||||
cacheProcessTimePerItem.push( processTimePerItem );
|
||||
this.resetDurationToHaveChangeOnProgress();
|
||||
} else {
|
||||
this.increaseDurationToHaveChangeOnProgress( autoSyncDuration );
|
||||
}
|
||||
|
||||
if ( ! cacheProcessTimePerItem.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return cacheProcessTimePerItem.reduce(
|
||||
( accumulator, processTime ) => accumulator + processTime, 0
|
||||
) / cacheProcessTimePerItem.length;
|
||||
},
|
||||
increaseDurationToHaveChangeOnProgress( increaseTime ) {
|
||||
durationToHaveChangeOnProgress += increaseTime;
|
||||
if ( durationToHaveChangeOnProgress > timeLimitToShowNotice ) {
|
||||
this.showHoldOnNotice();
|
||||
}
|
||||
},
|
||||
showHoldOnNotice() {
|
||||
holdOnNoticeElement.classList.remove( 'sui-hidden' );
|
||||
timeLimitToShowNotice = 100000000;
|
||||
},
|
||||
resetHoldOnNoticeVisibility() {
|
||||
holdOnNoticeElement.classList.add( 'sui-hidden' );
|
||||
},
|
||||
resetDurationToHaveChangeOnProgress() {
|
||||
durationToHaveChangeOnProgress = autoSyncDuration;
|
||||
},
|
||||
formatTime( totalMilliSeconds ) {
|
||||
const totalSeconds = Math.floor( ( totalMilliSeconds + progressTransitionDuration ) / 1000 );
|
||||
const seconds = totalSeconds % 60;
|
||||
const minutes = Math.floor( totalSeconds / 60 );
|
||||
|
||||
let timeString = '';
|
||||
if ( minutes ) {
|
||||
timeString += minutes + ' ' + _n( 'minute', 'minutes', minutes, 'wp-smushit' );
|
||||
}
|
||||
|
||||
timeString += ' ' + seconds + ' ' + _n( 'second', 'seconds', seconds, 'wp-smushit' );
|
||||
|
||||
return timeString.trim();
|
||||
},
|
||||
reset() {
|
||||
progressElement.style.width = '0%';
|
||||
percentElement.innerHTML = '0%';
|
||||
|
||||
this.resetCancelButton();
|
||||
this.resetHoldOnNoticeVisibility();
|
||||
return this;
|
||||
},
|
||||
open() {
|
||||
cancelBtn.onclick = onCancelCallback;
|
||||
scanProgressBar.classList.remove( 'sui-hidden' );
|
||||
},
|
||||
close() {
|
||||
scanProgressBar.classList.add( 'sui-hidden' );
|
||||
this.reset();
|
||||
},
|
||||
setOnCancelCallback( callBack ) {
|
||||
if ( 'function' !== typeof callBack ) {
|
||||
return;
|
||||
}
|
||||
onCancelCallback = callBack;
|
||||
return this;
|
||||
},
|
||||
setCancelButtonLabel( textContent ) {
|
||||
cancelBtn.textContent = textContent;
|
||||
return this;
|
||||
},
|
||||
setCancelButtonOnCancelling() {
|
||||
this.setCancelButtonLabel( wp_smush_msgs.cancelling );
|
||||
this.setOnCancelCallback( () => false );
|
||||
cancelBtn.setAttribute( 'disabled', true );
|
||||
},
|
||||
resetCancelButton() {
|
||||
this.setOnCancelCallback( () => {} );
|
||||
this.resetCancelButtonLabel();
|
||||
cancelBtn.removeAttribute( 'disabled' );
|
||||
},
|
||||
resetCancelButtonLabel() {
|
||||
this.setCancelButtonLabel( __( 'Cancel Scan', 'wp-smushit' ) );
|
||||
},
|
||||
resetCancelButtonOnFailure() {
|
||||
this.resetCancelButtonLabel();
|
||||
cancelBtn.removeAttribute( 'disabled' );
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const SmushProgressBar = () => {
|
||||
'use strict';
|
||||
const progressBar = document.querySelector( '.wp-smush-bulk-progress-bar-wrapper' );
|
||||
if ( ! progressBar ) {
|
||||
return {
|
||||
isEmptyObject: true,
|
||||
};
|
||||
}
|
||||
const cancelBtn = progressBar.querySelector( '.wp-smush-cancel-btn' );
|
||||
const bulkSmushDescription = document.querySelector( '.wp-smush-bulk-wrapper' );
|
||||
const bulkRunningNotice = progressBar.querySelector( '#wp-smush-running-notice' );
|
||||
const bulkSmushAllDone = document.querySelector( '.wp-smush-all-done' );
|
||||
const stopBulkSmushModal = document.getElementById( 'smush-stop-bulk-smush-modal' );
|
||||
const holdOnNoticeElement = progressBar.querySelector( '.wp-smush-bulk-hold-on-notice' );
|
||||
let isStateHidden = false;
|
||||
let onCancelCallback = () => {};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Update progress bar.
|
||||
*
|
||||
* @param {number} processedItems
|
||||
* @param {number} totalItems
|
||||
*/
|
||||
update( processedItems, totalItems ) {
|
||||
let width = totalItems && Math.floor( processedItems / totalItems * 100 ) || 0;
|
||||
width = Math.min( width, 100 );
|
||||
|
||||
// Progress bar label.
|
||||
progressBar.querySelector( '.wp-smush-images-percent' ).innerHTML = width + '%';
|
||||
// Progress bar.
|
||||
progressBar.querySelector( '.wp-smush-progress-inner' ).style.width = width + '%';
|
||||
|
||||
// Update processed/total.
|
||||
const processStateStats = progressBar.querySelector( '.sui-progress-state-text' );
|
||||
processStateStats.firstElementChild.innerHTML = processedItems;
|
||||
processStateStats.lastElementChild.innerHTML = totalItems;
|
||||
|
||||
return this;
|
||||
},
|
||||
close() {
|
||||
progressBar.classList.add( 'sui-hidden' );
|
||||
this.setCancelButtonLabel( window.wp_smush_msgs.cancel )
|
||||
.setOnCancelCallback( () => {} )
|
||||
.update( 0, 0 );
|
||||
this.resetOriginalNotice();
|
||||
this.closeStopBulkSmushModal();
|
||||
return this;
|
||||
},
|
||||
show() {
|
||||
// Show progress bar.
|
||||
progressBar.classList.remove( 'sui-hidden' );
|
||||
cancelBtn.onclick = this.showStopBulkSmushModal.bind( this );
|
||||
this.hideBulkSmushDescription();
|
||||
this.hideBulkSmushAllDone();
|
||||
this.hideRecheckImagesNotice();
|
||||
},
|
||||
showStopBulkSmushModal() {
|
||||
if ( ! stopBulkSmushModal ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stopBulkSmushButton = stopBulkSmushModal.querySelector( '.smush-stop-bulk-smush-button' );
|
||||
stopBulkSmushButton.addEventListener( 'click', onCancelCallback, { once: true } );
|
||||
|
||||
// Displays the modal with the release's higlights if it exists.
|
||||
const modalId = stopBulkSmushModal.id,
|
||||
focusAfterClosed = 'wpbody-content',
|
||||
focusWhenOpen = undefined,
|
||||
hasOverlayMask = false,
|
||||
isCloseOnEsc = false,
|
||||
isAnimated = true;
|
||||
|
||||
window.SUI.openModal(
|
||||
modalId,
|
||||
focusAfterClosed,
|
||||
focusWhenOpen,
|
||||
hasOverlayMask,
|
||||
isCloseOnEsc,
|
||||
isAnimated
|
||||
);
|
||||
},
|
||||
closeStopBulkSmushModal() {
|
||||
if ( ! window.SUI ) {
|
||||
return;
|
||||
}
|
||||
const isModalClosed = ( ! stopBulkSmushModal ) || ! stopBulkSmushModal.classList.contains( 'sui-content-fade-in' );
|
||||
if ( isModalClosed ) {
|
||||
return;
|
||||
}
|
||||
window.SUI.closeModal( stopBulkSmushModal.id );
|
||||
},
|
||||
setCancelButtonLabel( textContent ) {
|
||||
cancelBtn.textContent = textContent;
|
||||
return this;
|
||||
},
|
||||
showBulkSmushDescription() {
|
||||
bulkSmushDescription.classList.remove( 'sui-hidden' );
|
||||
},
|
||||
hideBulkSmushDescription() {
|
||||
bulkSmushDescription.classList.add( 'sui-hidden' );
|
||||
},
|
||||
showBulkSmushAllDone() {
|
||||
bulkSmushAllDone.classList.remove( 'sui-hidden' );
|
||||
},
|
||||
hideBulkSmushAllDone() {
|
||||
bulkSmushAllDone.classList.add( 'sui-hidden' );
|
||||
},
|
||||
hideState() {
|
||||
if ( isStateHidden ) {
|
||||
return this;
|
||||
}
|
||||
isStateHidden = true;
|
||||
progressBar.querySelector( '.sui-progress-state' ).classList.add( 'sui-hidden' );
|
||||
return this;
|
||||
},
|
||||
showState() {
|
||||
if ( ! isStateHidden ) {
|
||||
return this;
|
||||
}
|
||||
isStateHidden = false;
|
||||
progressBar.querySelector( '.sui-progress-state' ).classList.remove( 'sui-hidden' );
|
||||
return this;
|
||||
},
|
||||
setNotice( inProcessNotice ) {
|
||||
const progressMessage = bulkRunningNotice.querySelector( '.sui-notice-message p' );
|
||||
this.cacheOriginalNotice( progressMessage );
|
||||
progressMessage.innerHTML = inProcessNotice;
|
||||
return this;
|
||||
},
|
||||
cacheOriginalNotice( progressMessage ) {
|
||||
if ( bulkRunningNotice.dataset.progressMessage ) {
|
||||
return;
|
||||
}
|
||||
bulkRunningNotice.dataset.progressMessage = progressMessage.innerHTML;
|
||||
},
|
||||
resetOriginalNotice() {
|
||||
if ( ! bulkRunningNotice.dataset.progressMessage ) {
|
||||
return;
|
||||
}
|
||||
const progressMessage = bulkRunningNotice.querySelector( '.sui-notice-message p' );
|
||||
progressMessage.innerHTML = bulkRunningNotice.dataset.progressMessage;
|
||||
},
|
||||
hideBulkProcessingNotice() {
|
||||
bulkRunningNotice.classList.add( 'sui-hidden' );
|
||||
return this;
|
||||
},
|
||||
showBulkProcessingNotice() {
|
||||
bulkRunningNotice.classList.remove( 'sui-hidden' );
|
||||
return this;
|
||||
},
|
||||
setCountUnitText( unitText ) {
|
||||
const progressUnit = progressBar.querySelector( '.sui-progress-state-unit' );
|
||||
progressUnit.innerHTML = unitText;
|
||||
},
|
||||
setOnCancelCallback( callBack ) {
|
||||
if ( 'function' !== typeof callBack ) {
|
||||
return;
|
||||
}
|
||||
onCancelCallback = callBack;
|
||||
return this;
|
||||
},
|
||||
disableExceedLimitMode() {
|
||||
progressBar.classList.remove( 'wp-smush-exceed-limit' );
|
||||
progressBar.querySelector( '#bulk-smush-resume-button' ).classList.add( 'sui-hidden' );
|
||||
},
|
||||
hideRecheckImagesNotice() {
|
||||
const recheckImagesNoticeElement = document.querySelector( '.wp-smush-recheck-images-notice-box' );
|
||||
if ( recheckImagesNoticeElement ) {
|
||||
recheckImagesNoticeElement.classList.add( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
showHoldOnNotice() {
|
||||
if ( holdOnNoticeElement ) {
|
||||
holdOnNoticeElement.classList.remove( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
hideHoldOnNotice() {
|
||||
if ( holdOnNoticeElement ) {
|
||||
holdOnNoticeElement.classList.add( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
export default new SmushProgressBar();
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
import unique from 'unique-selector';
|
||||
import getXPath from 'get-xpath';
|
||||
|
||||
export class SmushLCPDetector {
|
||||
onLCP(data) {
|
||||
const element = data?.entries[0]?.element;
|
||||
const imageUrl = data?.attribution?.url;
|
||||
if (!element || !imageUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selector = unique(element);
|
||||
const xpath = getXPath(element, {ignoreId: true});
|
||||
this.useRelativeImageURL = this.shouldUseRelativeImageURL( element, imageUrl );
|
||||
const body = {
|
||||
url: window.location.href,
|
||||
data: JSON.stringify({
|
||||
selector: selector,
|
||||
selector_xpath: xpath,
|
||||
selector_id: element?.id,
|
||||
selector_class: element?.className,
|
||||
image_url: this.useRelativeImageURL ? this.makeImageURLRelative( imageUrl ) : imageUrl,
|
||||
background_data: this.getBackgroundDataForElement( element ),
|
||||
} ),
|
||||
nonce: smush_detector.nonce,
|
||||
is_mobile: smush_detector.is_mobile,
|
||||
data_store: JSON.stringify(smush_detector.data_store),
|
||||
previous_data_version: smush_detector.previous_data_version,
|
||||
previous_data_hash: smush_detector.previous_data_hash,
|
||||
};
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', smush_detector.ajax_url + '?action=smush_handle_lcp_data', true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
const urlEncodedData = Object.keys(body)
|
||||
.map(key => encodeURIComponent(key) + "=" + encodeURIComponent(body[key]))
|
||||
.join("&");
|
||||
xhr.send(urlEncodedData);
|
||||
}
|
||||
|
||||
shouldUseRelativeImageURL( element, absoluteImageUrl ) {
|
||||
if ( ! element?.outerHTML ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const outerHTML = element.outerHTML;
|
||||
const containsAbsoluteUrl = outerHTML.includes( absoluteImageUrl );
|
||||
|
||||
if ( containsAbsoluteUrl ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const relativeImageUrl = this.makeImageURLRelative( absoluteImageUrl );
|
||||
const containsRelativeUrl = outerHTML.includes( relativeImageUrl );
|
||||
|
||||
return containsRelativeUrl;
|
||||
}
|
||||
|
||||
makeImageURLRelative( imageUrl ) {
|
||||
try {
|
||||
const url = new URL( imageUrl, window.location.origin ); // Parse the URL
|
||||
if ( url.hostname === window.location.hostname ) {
|
||||
// Only make the URL relative if it belongs to the current host
|
||||
return url.pathname + url.search; // Keep the path and query string
|
||||
}
|
||||
} catch ( e ) {
|
||||
// If the URL is invalid or relative, return it as-is.
|
||||
}
|
||||
|
||||
return imageUrl; // Return the original URL if it doesn't belong to the host
|
||||
}
|
||||
|
||||
getBackgroundDataForElement(element) {
|
||||
const computedStyle = window.getComputedStyle(element, null);
|
||||
const backgroundProps = [
|
||||
computedStyle.getPropertyValue("background-image"),
|
||||
getComputedStyle(element, ":after").getPropertyValue("background-image"),
|
||||
getComputedStyle(element, ":before").getPropertyValue("background-image")
|
||||
].filter((prop) => prop !== "none");
|
||||
if (backgroundProps.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return this.getBackgroundDataForPropertyValue(backgroundProps[0]);
|
||||
}
|
||||
|
||||
getBackgroundDataForPropertyValue(fullBackgroundProp) {
|
||||
let type = "background-image";
|
||||
if (fullBackgroundProp.includes("image-set(")) {
|
||||
type = "background-image-set";
|
||||
}
|
||||
if (!fullBackgroundProp || fullBackgroundProp === "" || fullBackgroundProp.includes("data:image")) {
|
||||
return null;
|
||||
}
|
||||
// IMPORTANT: the following regex is a copy of the one in the PHP function Parser::get_image_urls. Remember to keep them synced.
|
||||
const cssBackgroundUrlRegex = /((?:https?:\/|\.+)?\/[^'",\s()]+\.(jpe?g|png|gif|webp|svg|avif)(?:\?[^\s'",?)]+)?)\b/ig;
|
||||
const matches = [ ...fullBackgroundProp.matchAll( cssBackgroundUrlRegex ) ];
|
||||
const backgroundSet = matches.map( ( match ) => {
|
||||
const imageURL = match[ 1 ].trim();
|
||||
|
||||
return this.useRelativeImageURL
|
||||
? this.makeImageURLRelative( imageURL )
|
||||
: imageURL;
|
||||
} );
|
||||
|
||||
if ( backgroundSet.length <= 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
urls: backgroundSet,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
(function () {
|
||||
let lcpEntry = null;
|
||||
let finalized = false;
|
||||
const initialViewportBottom = window.innerHeight;
|
||||
const pageLoadStartedAtTop = document?.documentElement?.scrollTop === 0;
|
||||
|
||||
if (!pageLoadStartedAtTop || !('PerformanceObserver' in window)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const po = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (isInInitialViewport(entry)) {
|
||||
lcpEntry = entry; // always keep the latest candidate
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
po.observe({type: 'largest-contentful-paint', buffered: true});
|
||||
} catch (e) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
function finalizeLCP() {
|
||||
if (finalized) {
|
||||
return;
|
||||
}
|
||||
finalized = true;
|
||||
|
||||
if (lcpEntry) {
|
||||
const detector = new SmushLCPDetector();
|
||||
detector.onLCP({
|
||||
entries: [lcpEntry],
|
||||
attribution: {
|
||||
url: lcpEntry.url || '',
|
||||
element: lcpEntry.element || ''
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (po) {
|
||||
po.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
function isInInitialViewport(entry) {
|
||||
const el = entry && entry.element;
|
||||
if (!el) {
|
||||
return true;
|
||||
}
|
||||
const rect = el.getBoundingClientRect();
|
||||
const elementTop = rect.top + window.scrollY;
|
||||
return elementTop <= initialViewportBottom;
|
||||
}
|
||||
|
||||
// Finalize on first *trusted* user input
|
||||
['keydown', 'click', 'pointerdown', 'touchstart'].forEach((type) => {
|
||||
addEventListener(type, (event) => {
|
||||
if (event.isTrusted) {
|
||||
finalizeLCP();
|
||||
}
|
||||
}, {once: true, capture: true});
|
||||
});
|
||||
})();
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import restoreLazySizesConfig from './lazy-load/helper/break-lazysizes';
|
||||
import lazySizes from './lazy-load/helper/lazysizes';
|
||||
require( './lazy-load/lazy-load-background-images' );
|
||||
require( './lazy-load/lazy-load-video' );
|
||||
|
||||
lazySizes.init();
|
||||
|
||||
restoreLazySizesConfig();
|
||||
+637
@@ -0,0 +1,637 @@
|
||||
import { isSmushLazySizesInstance } from './helper/lazysizes';
|
||||
|
||||
export const LAZY_BEFORE_SIZES = 'lazybeforesizes';
|
||||
export const SMUSH_BEFORE_SIZES = 'smush:beforeSizes';
|
||||
const SMUSH_CDN_DOMAIN = 'smushcdn.com';
|
||||
const ATTR_DATA_ORIGINAL_SIZES = 'data-original-sizes';
|
||||
const ATTR_DATA_SRCSET = 'data-srcset';
|
||||
const ATTR_DATA_SRC = 'data-src';
|
||||
const SUPPORTED_EXTENSIONS = [ 'gif', 'jpg', 'jpeg', 'png', 'webp' ];
|
||||
const SRCSET_WIDTH_DESCRIPTOR = 'w';
|
||||
const SRCSET_DENSITY_DESCRIPTOR = 'x';
|
||||
/**
|
||||
* Class representing lazy loading functionality with CDN support.
|
||||
*/
|
||||
export class AutoResizing {
|
||||
/**
|
||||
* Create a SmushLazyload instance.
|
||||
*
|
||||
* @param {Object} [options={}] - Auto resize options for the instance.
|
||||
* @param {number} [options.precision=0] - Allowed width variation (in pixels) for determining if resizing is necessary.
|
||||
* @param {boolean} [options.skipAutoWidth=false] - Whether to skip auto width resizing.
|
||||
*/
|
||||
constructor( { precision = 0, skipAutoWidth = false } = {} ) {
|
||||
this.precision = parseInt( precision, 10 );
|
||||
this.precision = isNaN( this.precision ) ? 0 : this.precision;
|
||||
this.skipAutoWidth = skipAutoWidth;
|
||||
|
||||
this.initEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize event listeners.
|
||||
*/
|
||||
initEventListeners() {
|
||||
document.addEventListener( LAZY_BEFORE_SIZES, ( e ) => {
|
||||
if ( ! isSmushLazySizesInstance( e.detail?.instance ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.maybeAutoResize( e );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto resize for CDN images.
|
||||
*
|
||||
* @param {Object} lazyEvent - Event object.
|
||||
* @return {void}
|
||||
*/
|
||||
maybeAutoResize( lazyEvent ) {
|
||||
const element = lazyEvent.target;
|
||||
let resizeWidth = lazyEvent.detail?.width;
|
||||
const isImage = 'IMG' === element?.nodeName;
|
||||
|
||||
// Exit early if the element is not an image or resizeWidth is missing.
|
||||
if ( ! isImage || ! resizeWidth ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isInitialRender = lazyEvent.detail?.dataAttr;
|
||||
|
||||
// Skip processing if it's not the initial render.
|
||||
if ( ! isInitialRender ) {
|
||||
if ( ! this.getOriginalSizesAttr( element ) ) {
|
||||
lazyEvent.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the element is eligible for resizing.
|
||||
if ( ! this.isElementEligibleForResizing( element ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle reverting to original sizes if necessary.
|
||||
if ( this.shouldRevertToOriginalSizes( element, resizeWidth ) ) {
|
||||
if ( this.revertToOriginalSizesIfNeeded( element ) ) {
|
||||
// Prevent lazySizes from resizing the image.
|
||||
lazyEvent.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const customEvent = this.triggerEvent( element, SMUSH_BEFORE_SIZES, {
|
||||
resizeWidth
|
||||
} );
|
||||
|
||||
if ( customEvent.defaultPrevented ) {
|
||||
// If the event is prevented, do not proceed with resizing and revert the sizes.
|
||||
if ( this.revertToOriginalSizesIfNeeded( element ) ) {
|
||||
// Prevent lazySizes from resizing the image.
|
||||
lazyEvent.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
resizeWidth = customEvent.detail?.resizeWidth || resizeWidth;
|
||||
|
||||
// Resize the image using CDN if applicable.
|
||||
const src = this.getDataSrc( element );
|
||||
if ( this.isFromSmushCDN( src ) ) {
|
||||
this.resizeImageWithCDN( element, resizeWidth );
|
||||
|
||||
if ( this.isChildOfPicture( element ) ) {
|
||||
this.resizeSourceElements( element.parentNode.querySelectorAll( 'source' ), resizeWidth );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether Smush should apply auto-resize for this image.
|
||||
*
|
||||
* Rules:
|
||||
* 1. If wrapper is inline/inline-block and wrapper/image already equal resizeWidth, skip (prevents Divi shrink).
|
||||
* 2. Otherwise, allow.
|
||||
*
|
||||
* @param imageElement
|
||||
* @param resizeWidth
|
||||
*/
|
||||
/**
|
||||
* Decide whether Smush should apply auto-resize for this image.
|
||||
*
|
||||
* Rules:
|
||||
* 1. If wrapper is inline/inline-block and wrapper/image already equal resizeWidth, skip (prevents Divi shrink).
|
||||
* 2. Otherwise, allow.
|
||||
*
|
||||
* @param imageElement
|
||||
* @param resizeWidth
|
||||
*/
|
||||
shouldAutoResize( imageElement, resizeWidth ) {
|
||||
const wrapper = imageElement.parentNode;
|
||||
|
||||
if ( wrapper && this.isInlineElement( wrapper ) ) {
|
||||
if ( 'PICTURE' === wrapper.nodeName ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const wrapperWidth = wrapper.clientWidth;
|
||||
const imageWidth = imageElement.offsetWidth;
|
||||
const isWrapperAndImageSameWidth = resizeWidth === wrapperWidth && wrapperWidth === imageWidth;
|
||||
|
||||
if ( isWrapperAndImageSameWidth ) {
|
||||
// BAIL: doing a resize here risks shrinking the inline wrapper
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
isInlineElement( el ) {
|
||||
if ( ! el || el.nodeType !== 1 ) {
|
||||
return false;
|
||||
}
|
||||
const display = window.getComputedStyle( el ).display;
|
||||
return display === 'inline' || display === 'inline-block';
|
||||
}
|
||||
|
||||
isChildOfPicture( imageElement ) {
|
||||
return imageElement && 'PICTURE' === imageElement?.parentNode?.nodeName;
|
||||
}
|
||||
|
||||
resizeSourceElements( sourceElements, resizeWidth ) {
|
||||
if ( ! sourceElements || ! sourceElements?.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourceElements.forEach( ( sourceElement ) => this.resizeSourceElement( sourceElement, resizeWidth ) );
|
||||
}
|
||||
|
||||
resizeSourceElement( sourceElement, resizeWidth ) {
|
||||
const srcset = sourceElement.getAttribute( ATTR_DATA_SRCSET );
|
||||
if ( ! srcset ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sortedSources = this.parseSrcSet( srcset );
|
||||
if ( ! sortedSources || ! sortedSources.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isNonResponsive = 1 === sortedSources.length && '' === sortedSources[ 0 ].unit;
|
||||
if ( isNonResponsive ) {
|
||||
this.resizeNonResponsiveSource( sourceElement, sortedSources[ 0 ].src, resizeWidth );
|
||||
return;
|
||||
}
|
||||
|
||||
const baseSourceSrc = this.getBaseSourceSrcForResize( sortedSources, resizeWidth );
|
||||
if ( ! this.isFromSmushCDN( baseSourceSrc ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateSrcsetForResize( sourceElement, srcset, baseSourceSrc, resizeWidth, sortedSources );
|
||||
}
|
||||
|
||||
resizeNonResponsiveSource(sourceElement, sourceSrc, resizeWidth ) {
|
||||
if ( ! this.isFromSmushCDN( sourceSrc ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! this.isSourceActive( sourceElement ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newSrcset = this.getResizedCDNURL( sourceSrc, resizeWidth );
|
||||
|
||||
// Add a new retina source to the srcset if no similar source exists for the retina width.
|
||||
const scale = this.getPixelRatio();
|
||||
if ( scale > 1 ) {
|
||||
const retinaWidth = Math.ceil( resizeWidth * scale );
|
||||
const retinaCDNURL = this.getResizedCDNURL( sourceSrc, retinaWidth );
|
||||
const newRetinaSourceString = retinaCDNURL + ' ' + retinaWidth + SRCSET_WIDTH_DESCRIPTOR;
|
||||
newSrcset += ` ${ resizeWidth }${ SRCSET_WIDTH_DESCRIPTOR }, ${ newRetinaSourceString }`;
|
||||
}
|
||||
|
||||
// Update the element's data-srcset attribute if the srcset has changed.
|
||||
this.updateElementSrcset( sourceElement, null, newSrcset );
|
||||
}
|
||||
|
||||
isSourceActive( sourceElement ) {
|
||||
const media = sourceElement.getAttribute( 'media' );
|
||||
if ( media && ! window?.matchMedia( media )?.matches ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getBaseSourceSrcForResize( sortedSources, resizeWidth ) {
|
||||
const largestSource = sortedSources[ 0 ];
|
||||
|
||||
if ( SRCSET_WIDTH_DESCRIPTOR !== largestSource.unit ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
! this.isThumbnail( largestSource.src ) ||
|
||||
largestSource.value >= resizeWidth
|
||||
) {
|
||||
return largestSource.src;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
isElementEligibleForResizing( element ) {
|
||||
const existOriginalSizes = this.getOriginalSizesAttr( element );
|
||||
const existSrc = this.getDataSrc( element );
|
||||
const existSrcSet = this.getDataSrcSet( element );
|
||||
/**
|
||||
* lazybeforesizes only fires for images with data-sizes="auto",
|
||||
* so skip checking it.
|
||||
*/
|
||||
return Boolean( existOriginalSizes && existSrc && existSrcSet );
|
||||
}
|
||||
|
||||
shouldRevertToOriginalSizes( element, resizeWidth ) {
|
||||
const imageWidth = this.getElementWidth( element );
|
||||
|
||||
// Skip resizing if width is 'auto' and skipping is enabled.
|
||||
if ( imageWidth === 'auto' ) {
|
||||
return this.shouldSkipAutoWidth();
|
||||
}
|
||||
|
||||
const originalSizes = this.getOriginalSizesAttr( element );
|
||||
const maxWidthFromSizes = this.getMaxWidthFromSizes( originalSizes );
|
||||
if ( maxWidthFromSizes && resizeWidth > maxWidthFromSizes && ! this.isChildOfPicture( element ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! this.shouldAutoResize( element, resizeWidth );
|
||||
}
|
||||
|
||||
triggerEvent( elem, name, detail = {}, bubbles = true, cancelable = true ) {
|
||||
const event = new CustomEvent( name, {
|
||||
detail,
|
||||
bubbles,
|
||||
cancelable,
|
||||
} );
|
||||
|
||||
elem.dispatchEvent( event );
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if auto width resizing should be skipped.
|
||||
*
|
||||
* @return {boolean} - True if auto width resizing should be skipped, false otherwise.
|
||||
*/
|
||||
shouldSkipAutoWidth() {
|
||||
return this.skipAutoWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the image using the CDN to generate appropriate sizes for the target width.
|
||||
*
|
||||
* @param {HTMLElement} element - The image element to resize.
|
||||
* @param {number} resizeWidth - The target width for the image.
|
||||
*/
|
||||
resizeImageWithCDN( element, resizeWidth ) {
|
||||
const srcset = this.getDataSrcSet( element );
|
||||
const src = this.getDataSrc( element );
|
||||
|
||||
// Exit early if the srcset or src is missing.
|
||||
if ( ! srcset || ! src ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the srcset once and reuse the parsed sources.
|
||||
const sortedSources = this.parseSrcSet( srcset );
|
||||
|
||||
//the src attribute can be a thumbnail, so we need to get the larger image url to resize from it.
|
||||
const baseImageSrc = this.getBaseImageSrcForResize( src, sortedSources, resizeWidth );
|
||||
|
||||
this.updateSrcsetForResize( element, srcset, baseImageSrc, resizeWidth, sortedSources );
|
||||
}
|
||||
|
||||
updateSrcsetForResize( element, srcset, baseImageSrc, resizeWidth, sources ) {
|
||||
// Update the srcset with the target width.
|
||||
let newSrcset = this.updateSrcsetWithTargetWidth( srcset, baseImageSrc, resizeWidth, sources );
|
||||
|
||||
// Update the srcset with retina-specific widths if applicable.
|
||||
newSrcset = this.updateSrcsetWithRetinaWidth( newSrcset, baseImageSrc, resizeWidth, sources );
|
||||
|
||||
// Update the element's data-srcset attribute if the srcset has changed.
|
||||
this.updateElementSrcset( element, srcset, newSrcset );
|
||||
}
|
||||
|
||||
getBaseImageSrcForResize( src, sortedSources, resizeWidth ) {
|
||||
if ( ! this.isThumbnail( src ) ) {
|
||||
return src;
|
||||
}
|
||||
|
||||
// Find the largest source that is larger than resizing width.
|
||||
const largerSource = sortedSources.find( ( source ) => {
|
||||
return source.value >= resizeWidth;
|
||||
} );
|
||||
|
||||
return largerSource ? largerSource.src : src;
|
||||
}
|
||||
|
||||
isThumbnail( src ) {
|
||||
// Find the largest source that is larger than the current src.
|
||||
const regex = new RegExp( `(-\\d+x\\d+)\\.(${ SUPPORTED_EXTENSIONS.join( '|' ) })(?:\\?.+)?$`, 'i' );
|
||||
|
||||
return regex.test( src );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the srcset with the target width if no similar source exists.
|
||||
*
|
||||
* @param {string} srcset - The current srcset string.
|
||||
* @param {string} src - The original source URL of the image.
|
||||
* @param {number} resizeWidth - The target width for the image.
|
||||
* @param {Array} sources - The parsed sources from the srcset.
|
||||
* @return {string} The updated srcset string.
|
||||
*/
|
||||
updateSrcsetWithTargetWidth( srcset, src, resizeWidth, sources ) {
|
||||
// Add a new source to the srcset if no similar source exists for the target width.
|
||||
if ( ! this.findSimilarSource( sources, resizeWidth ) ) {
|
||||
const resizedCDNURL = this.getResizedCDNURL( src, resizeWidth );
|
||||
return srcset + ', ' + resizedCDNURL + ' ' + resizeWidth + SRCSET_WIDTH_DESCRIPTOR;
|
||||
}
|
||||
|
||||
return srcset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the srcset with retina-specific widths if applicable.
|
||||
*
|
||||
* @param {string} srcset - The current srcset string.
|
||||
* @param {string} src - The original source URL of the image.
|
||||
* @param {number} resizeWidth - The target width for the image.
|
||||
* @param {Array} sources - The parsed sources from the srcset.
|
||||
* @return {string} The updated srcset string.
|
||||
*/
|
||||
updateSrcsetWithRetinaWidth( srcset, src, resizeWidth, sources ) {
|
||||
const scale = this.getPixelRatio();
|
||||
if ( scale <= 1 ) {
|
||||
return srcset;
|
||||
}
|
||||
|
||||
const retinaWidth = Math.ceil( resizeWidth * scale );
|
||||
const hasRetinaSource = this.findSimilarSource( sources, scale, SRCSET_DENSITY_DESCRIPTOR ) ||
|
||||
this.findSimilarSource( sources, retinaWidth, SRCSET_WIDTH_DESCRIPTOR );
|
||||
|
||||
if ( hasRetinaSource ) {
|
||||
return srcset;
|
||||
}
|
||||
|
||||
// Add a new retina source to the srcset if no similar source exists for the retina width.
|
||||
const retinaCDNURL = this.getResizedCDNURL( src, retinaWidth );
|
||||
const newRetinaSourceString = retinaCDNURL + ' ' + retinaWidth + SRCSET_WIDTH_DESCRIPTOR;
|
||||
|
||||
return srcset + ', ' + newRetinaSourceString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the element's data-srcset attribute if the srcset has changed.
|
||||
*
|
||||
* @param {HTMLElement} element - The image element to update.
|
||||
* @param {string} originalSrcset - The original srcset string.
|
||||
* @param {string} newSrcset - The updated srcset string.
|
||||
*/
|
||||
updateElementSrcset( element, originalSrcset, newSrcset ) {
|
||||
if ( newSrcset !== originalSrcset ) {
|
||||
element.setAttribute( 'data-srcset', newSrcset );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the device pixel ratio.
|
||||
*
|
||||
* @return {number} The device pixel ratio. Default is 1 if the property is not available.
|
||||
*/
|
||||
getPixelRatio() {
|
||||
return window.devicePixelRatio || 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns the first source object that has a similar width to the target width.
|
||||
*
|
||||
* @param {Array} sources - An array of source objects to search through.
|
||||
* @param {number} resizeWidth - The target width to match against the source widths.
|
||||
* @param {string} [unit='w'] - The unit of measurement for the width (default is 'w').
|
||||
* @param {number} [precision=this.precision] - The allowed width variation (in pixels) used to determine if a source width matches the target width during resizing.
|
||||
* @return {Object|null} - The first source object that matches the criteria, or null if no match is found.
|
||||
*/
|
||||
findSimilarSource( sources, resizeWidth, unit = SRCSET_WIDTH_DESCRIPTOR, precision = this.precision ) {
|
||||
return sources.find( ( source ) => {
|
||||
return unit === source.unit && source.value >= resizeWidth &&
|
||||
this.isFuzzyMatch( source.value, resizeWidth, precision );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resized image CDN URL.
|
||||
*
|
||||
* @param {string} src - The original source URL of the image.
|
||||
* @param {number} resizeWidth - The target width for the resized image.
|
||||
* @return {string|undefined} The resized image CDN URL, or undefined if resizing is not applicable.
|
||||
*/
|
||||
getResizedCDNURL( src, resizeWidth ) {
|
||||
const url = this.parseURL( src );
|
||||
if ( ! url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams( url.search );
|
||||
searchParams.set( 'size', `${ resizeWidth }x0` );
|
||||
// Get the base URL (without search parameters).
|
||||
const baseUrl = url.origin + url.pathname;
|
||||
|
||||
return `${ baseUrl }?${ searchParams.toString() }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the URL from the source string.
|
||||
*
|
||||
* @param {string} src - The source URL string.
|
||||
* @return {URL|null} The parsed URL object, or null if parsing fails.
|
||||
*/
|
||||
parseURL( src ) {
|
||||
try {
|
||||
return new URL( src );
|
||||
} catch ( error ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract width, unit, and src from srcset.
|
||||
*
|
||||
* @param {string} srcset - The srcset string.
|
||||
* @return {Array} An array of objects source info.
|
||||
*/
|
||||
parseSrcSet( srcset ) {
|
||||
const sources = this.extractSourcesFromSrcSet( srcset );
|
||||
return this.sortSources( sources );
|
||||
}
|
||||
|
||||
extractSourcesFromSrcSet( srcset ) {
|
||||
return srcset.split( ',' ).map( ( item ) => {
|
||||
const [ src, descriptor ] = item.trim().split( /\s+/ );
|
||||
let value = 0;
|
||||
let unit = '';
|
||||
|
||||
if ( descriptor ) {
|
||||
if ( descriptor.endsWith( SRCSET_WIDTH_DESCRIPTOR ) ) {
|
||||
value = parseInt( descriptor, 10 );
|
||||
unit = SRCSET_WIDTH_DESCRIPTOR;
|
||||
} else if ( descriptor.endsWith( SRCSET_DENSITY_DESCRIPTOR ) ) {
|
||||
value = parseFloat( descriptor );
|
||||
unit = SRCSET_DENSITY_DESCRIPTOR;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
markup: item,
|
||||
src,
|
||||
value,
|
||||
unit,
|
||||
};
|
||||
} );
|
||||
}
|
||||
|
||||
sortSources( sources ) {
|
||||
sources.sort( ( a, b ) => {
|
||||
if ( a.value === b.value ) {
|
||||
return 0;
|
||||
}
|
||||
return a.value > b.value ? -1 : 1;
|
||||
} );
|
||||
return sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert to the original sizes attribute.
|
||||
*
|
||||
* @param {Object} element - Image element.
|
||||
* @return {boolean} True if the original sizes were reverted, false otherwise.
|
||||
*/
|
||||
revertToOriginalSizesIfNeeded( element ) {
|
||||
const originalSizes = this.getOriginalSizesAttr( element );
|
||||
if ( originalSizes ) {
|
||||
element.setAttribute( 'sizes', originalSizes );
|
||||
element.removeAttribute( ATTR_DATA_ORIGINAL_SIZES );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image width.
|
||||
*
|
||||
* @param {Object} element - Image element.
|
||||
* @return {string|number} The image width.
|
||||
*/
|
||||
getElementWidth( element ) {
|
||||
/**
|
||||
* Check if the element has an inline width set to 'auto'.
|
||||
* Note: For external CSS, we couldn't cover it due to getComputedStyle just returning the parsed value.
|
||||
*/
|
||||
const inlineWidth = element.style?.width;
|
||||
if ( inlineWidth && 'auto' === inlineWidth.trim() ) {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
const widthStr = window.getComputedStyle( element ).width;
|
||||
const width = parseInt( widthStr, 10 );
|
||||
|
||||
return isNaN( width ) ? widthStr : width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content width from the original sizes attribute.
|
||||
*
|
||||
* @param {string} originalSizes - The original sizes attribute.
|
||||
* @return {number} The content width.
|
||||
*/
|
||||
getMaxWidthFromSizes( originalSizes ) {
|
||||
const regex = /\(max-width:\s*(\d+)px\)\s*100vw,\s*\1px/;
|
||||
const match = originalSizes.match( regex );
|
||||
|
||||
return match ? parseInt( match[ 1 ], 10 ) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original sizes attribute.
|
||||
*
|
||||
* @param {Object} element - Image element.
|
||||
* @return {string} The original sizes attribute.
|
||||
*/
|
||||
getOriginalSizesAttr( element ) {
|
||||
return element.getAttribute( ATTR_DATA_ORIGINAL_SIZES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the srcset attribute.
|
||||
*
|
||||
* @param {Object} element - Image element.
|
||||
* @return {string} The srcset attribute.
|
||||
*/
|
||||
getDataSrcSet( element ) {
|
||||
return element.getAttribute( ATTR_DATA_SRCSET );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the src attribute.
|
||||
*
|
||||
* @param {Object} element - Image element.
|
||||
* @return {string} The src attribute.
|
||||
*/
|
||||
getDataSrc( element ) {
|
||||
return element.getAttribute( ATTR_DATA_SRC );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the source is from the CDN.
|
||||
*
|
||||
* @param {string} src - The source URL.
|
||||
* @return {boolean} True if the source is from the CDN, false otherwise.
|
||||
*/
|
||||
isFromSmushCDN( src ) {
|
||||
return src && src.includes( SMUSH_CDN_DOMAIN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a fuzzy match between two numbers.
|
||||
*
|
||||
* @param {number} number1 - The first number.
|
||||
* @param {number} number2 - The second number.
|
||||
* @param {number} [precision=1] - The allowed variation. Default is 1.
|
||||
* @return {boolean} True if the numbers are close enough, false otherwise.
|
||||
*/
|
||||
isFuzzyMatch( number1, number2, precision = 1 ) {
|
||||
return Math.abs( number1 - number2 ) <= precision;
|
||||
}
|
||||
}
|
||||
( () => {
|
||||
'use strict';
|
||||
const isAutoResizingEnabled = window.smushLazyLoadOptions?.autoResizingEnabled;
|
||||
if ( ! isAutoResizingEnabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let autoResizeOptions = window.smushLazyLoadOptions?.autoResizeOptions || {};
|
||||
autoResizeOptions = Object.assign(
|
||||
{
|
||||
precision: 5, //5px.
|
||||
skipAutoWidth: true, // Whether to skip the image has 'auto' width.
|
||||
},
|
||||
autoResizeOptions
|
||||
);
|
||||
new AutoResizing( autoResizeOptions );
|
||||
} )();
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @see https://github.com/aFarkas/lazysizes/issues/643#issuecomment-486168297
|
||||
* Or https://github.com/aFarkas/lazysizes/issues/647#issuecomment-487724519
|
||||
*/
|
||||
const originalLazySizesConfig = window.lazySizesConfig || null;
|
||||
if ( originalLazySizesConfig ) {
|
||||
delete window.lazySizesConfig;
|
||||
}
|
||||
|
||||
export default () => {
|
||||
// Restore the original lazySizesConfig if it was set before.
|
||||
if ( originalLazySizesConfig ) {
|
||||
window.lazySizesConfig = originalLazySizesConfig;
|
||||
} else if ( 'lazySizesConfig' in window ) {
|
||||
delete window.lazySizesConfig;
|
||||
}
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import lazySizes from 'lazysizes';
|
||||
|
||||
/*
|
||||
* TODO: Change the lazyClass name to be more specific to avoid conflicts with other plugins
|
||||
* in the case that they are using the same default class name.
|
||||
* @see https://github.com/aFarkas/lazysizes/issues/643#issuecomment-486168297
|
||||
* or https://github.com/aFarkas/lazysizes/issues/647#issuecomment-487724519
|
||||
*/
|
||||
|
||||
export const isSmushLazySizesInstance = ( instance ) => {
|
||||
return instance === lazySizes ||
|
||||
( JSON.stringify( instance?.cfg || {} ) === JSON.stringify( lazySizes?.cfg || {} ) );
|
||||
};
|
||||
|
||||
export default lazySizes;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { isSmushLazySizesInstance } from './helper/lazysizes';
|
||||
|
||||
( () => {
|
||||
'use strict';
|
||||
// Lazyload for background images.
|
||||
const lazyloadBackground = ( element ) => {
|
||||
const backgroundValue = element.getAttribute( 'data-bg-image' ) || element.getAttribute( 'data-bg' );
|
||||
const cssProperty = element.hasAttribute( 'data-bg-image' ) ? 'background-image' : 'background';
|
||||
|
||||
if ( backgroundValue ) {
|
||||
const currentStyle = element.getAttribute( 'style' ) || '';
|
||||
const newBackgroundCSS = `${ cssProperty }: ${ backgroundValue };`;
|
||||
const backgroundRegex = new RegExp( `${ cssProperty }\\s*:\\s*[^;]+;?` );
|
||||
|
||||
let updatedStyle;
|
||||
if ( backgroundRegex.test( currentStyle ) ) {
|
||||
updatedStyle = currentStyle.replace( backgroundRegex, newBackgroundCSS );
|
||||
} else {
|
||||
updatedStyle = currentStyle.length > 0 ? currentStyle.replace( /;$/g, '' ) + ';' + newBackgroundCSS : newBackgroundCSS;
|
||||
}
|
||||
|
||||
element.setAttribute( 'style', updatedStyle.trim() );
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener( 'lazybeforeunveil', function( e ) {
|
||||
if ( ! isSmushLazySizesInstance( e?.detail?.instance ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lazy background image.
|
||||
lazyloadBackground( e.target );
|
||||
} );
|
||||
} )();
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
import { isSmushLazySizesInstance } from './helper/lazysizes';
|
||||
|
||||
( () => {
|
||||
'use strict';
|
||||
// Constants
|
||||
const VIDEO_WRAPPER_CLASS = 'smush-lazyload-video';
|
||||
const LAZY_LOADED_CLASS = 'smush-lazyloaded-video';
|
||||
const AUTO_PLAY_CLASS = 'smush-lazyload-autoplay';
|
||||
const PLAY_BUTTON_CLASS = 'smush-play-btn';
|
||||
const DEFAULT_ALLOW_ATTR = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture';
|
||||
const USER_INTERACTION_EVENT = 'ontouchstart' in window ? 'touchstart' : 'pointerdown';
|
||||
const FALLBACK_VIDEO_RENDER_DELAY = Number( window?.smush_video_render_delay ) || 0;
|
||||
|
||||
/**
|
||||
* LazyLoadVideo Class
|
||||
* Handles lazy loading and autoplay functionality for videos.
|
||||
*/
|
||||
class LazyLoadVideo {
|
||||
constructor() {
|
||||
this.shouldDelayVideoRenderingForMobile = this.supportsIntersectionObserver();
|
||||
this.queuedVideoElements = [];
|
||||
this.isMobileOrSafari = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize event listeners and fallback mechanisms.
|
||||
*/
|
||||
init() {
|
||||
document.addEventListener( 'lazybeforeunveil', ( e ) => this.handleVideoLazyLoad( e ) );
|
||||
document.addEventListener(
|
||||
USER_INTERACTION_EVENT,
|
||||
() => this.enableVideoRenderingForMobile(),
|
||||
{ once: true, passive: true }
|
||||
);
|
||||
|
||||
// Unified fallback for delayed video rendering.
|
||||
const maybeTriggerVideoRenderingFallbackForMobile = () => {
|
||||
if ( FALLBACK_VIDEO_RENDER_DELAY <= 0 ) {
|
||||
const hasAutoPlayVideo = document.querySelector( `.${ VIDEO_WRAPPER_CLASS }.${ AUTO_PLAY_CLASS }` );
|
||||
if ( hasAutoPlayVideo ) {
|
||||
this.enableVideoRenderingForMobile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout( () => this.enableVideoRenderingForMobile(), FALLBACK_VIDEO_RENDER_DELAY );
|
||||
};
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', maybeTriggerVideoRenderingFallbackForMobile );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles lazy loading of video elements.
|
||||
*
|
||||
* @param {Event} e - The lazybeforeunveil event.
|
||||
*/
|
||||
handleVideoLazyLoad( e ) {
|
||||
const videoWrapper = e.target;
|
||||
|
||||
if (
|
||||
! isSmushLazySizesInstance( e?.detail?.instance ) ||
|
||||
! videoWrapper.classList.contains( VIDEO_WRAPPER_CLASS )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleButtonPlay( videoWrapper );
|
||||
this.maybePrepareVideoForPlay( videoWrapper );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the play button click event.
|
||||
*
|
||||
* @param {HTMLElement} videoWrapper - The video wrapper element.
|
||||
*/
|
||||
handleButtonPlay( videoWrapper ) {
|
||||
const playButton = videoWrapper.querySelector( `.${ PLAY_BUTTON_CLASS }` );
|
||||
|
||||
if ( playButton ) {
|
||||
const playHandler = () => this.loadIframeVideoWithAutoPlay( videoWrapper );
|
||||
playButton.addEventListener( 'click', playHandler );
|
||||
playButton.addEventListener( 'keydown', ( e ) => {
|
||||
if ( e.key === 'Enter' || e.key === ' ' ) {
|
||||
e.preventDefault();
|
||||
playHandler();
|
||||
}
|
||||
} );
|
||||
} else {
|
||||
this.loadIframeVideo( videoWrapper );
|
||||
window.console?.warning( 'Missing play button [.smush-play-btn] for video element:', videoWrapper );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the video for play based on autoplay and device conditions.
|
||||
*
|
||||
* @param {HTMLElement} videoWrapper - The video wrapper element.
|
||||
*/
|
||||
maybePrepareVideoForPlay( videoWrapper ) {
|
||||
const shouldAutoPlay = videoWrapper.classList.contains( AUTO_PLAY_CLASS );
|
||||
|
||||
if ( this.shouldPrepareIframeForPlay() ) {
|
||||
this.maybePrepareVideoForMobileAndSafari( videoWrapper, shouldAutoPlay );
|
||||
} else if ( shouldAutoPlay ) {
|
||||
this.loadIframeVideoWithAutoPlay( videoWrapper );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables video rendering for mobile devices.
|
||||
*/
|
||||
enableVideoRenderingForMobile() {
|
||||
if ( ! this.shouldDelayVideoRenderingForMobile ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shouldDelayVideoRenderingForMobile = false;
|
||||
this.maybeObserveQueuedVideoElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the browser supports IntersectionObserver.
|
||||
*
|
||||
* @return {boolean} True if supported, false otherwise.
|
||||
*/
|
||||
supportsIntersectionObserver() {
|
||||
return 'IntersectionObserver' in window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes queued video elements for lazy loading.
|
||||
*/
|
||||
maybeObserveQueuedVideoElements() {
|
||||
if ( this.queuedVideoElements.length ) {
|
||||
this.observeQueuedVideoElements();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes video elements using IntersectionObserver.
|
||||
*/
|
||||
observeQueuedVideoElements() {
|
||||
const observer = new IntersectionObserver(
|
||||
( entries ) => {
|
||||
entries.forEach( ( entry ) => {
|
||||
if ( entry.isIntersecting ) {
|
||||
const videoWrapper = entry.target;
|
||||
|
||||
this.loadIframeVideo( videoWrapper );
|
||||
observer.unobserve( videoWrapper );
|
||||
}
|
||||
} );
|
||||
},
|
||||
{
|
||||
rootMargin: '0px 0px 200px 0px',
|
||||
threshold: 0.1,
|
||||
}
|
||||
);
|
||||
|
||||
this.queuedVideoElements.forEach( ( videoWrapper ) => {
|
||||
observer.observe( videoWrapper );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares video for mobile and Safari browsers.
|
||||
*
|
||||
* @param {HTMLElement} videoWrapper - The video wrapper element.
|
||||
* @param {boolean} shouldAutoPlay - Whether the video should autoplay.
|
||||
*/
|
||||
maybePrepareVideoForMobileAndSafari( videoWrapper, shouldAutoPlay ) {
|
||||
if ( this.shouldDelayVideoRenderingForMobile ) {
|
||||
this.queuedVideoElements.push( videoWrapper );
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadIframeVideo( videoWrapper, shouldAutoPlay );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the iframe should be prepared for play.
|
||||
*
|
||||
* @return {boolean} True if preparation is needed, false otherwise.
|
||||
*/
|
||||
shouldPrepareIframeForPlay() {
|
||||
if ( this.isMobileOrSafari === null ) {
|
||||
this.isMobileOrSafari = this.checkIfMobileOrSafari();
|
||||
}
|
||||
|
||||
return this.isMobileOrSafari;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the device is mobile or Safari browser.
|
||||
*
|
||||
* @return {boolean} True if mobile or Safari, false otherwise.
|
||||
*/
|
||||
checkIfMobileOrSafari() {
|
||||
const userAgent = navigator.userAgent;
|
||||
return userAgent.includes( 'Mobi' ) || ( userAgent.includes( 'Safari' ) && ! userAgent.includes( 'Chrome' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the iframe video.
|
||||
*
|
||||
* @param {HTMLElement} videoWrapper - The video wrapper element.
|
||||
* @param {boolean} [autoPlay=false] - Whether to autoplay the video.
|
||||
*/
|
||||
loadIframeVideo( videoWrapper, autoPlay = false ) {
|
||||
if ( videoWrapper.classList.contains( LAZY_LOADED_CLASS ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
videoWrapper.classList.add( LAZY_LOADED_CLASS, 'loading' );
|
||||
|
||||
const iframe = videoWrapper.querySelector( 'iframe' );
|
||||
if ( ! iframe ) {
|
||||
window.console?.error( 'Missing iframe element in video wrapper:', videoWrapper );
|
||||
return;
|
||||
}
|
||||
|
||||
let videoUrl = iframe.dataset?.src;
|
||||
if ( ! videoUrl ) {
|
||||
window.console?.error( 'Missing data-src attribute for iframe:', iframe );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( autoPlay ) {
|
||||
const url = new URL( videoUrl );
|
||||
url.searchParams.set( 'autoplay', '1' );
|
||||
url.searchParams.set( 'playsinline', '1' );
|
||||
videoUrl = url.toString();
|
||||
}
|
||||
|
||||
let allowAttribute = iframe.getAttribute( 'allow' ) || DEFAULT_ALLOW_ATTR;
|
||||
if ( ! allowAttribute.includes( 'autoplay' ) ) {
|
||||
allowAttribute += '; autoplay';
|
||||
}
|
||||
|
||||
iframe.setAttribute( 'allow', allowAttribute );
|
||||
iframe.setAttribute( 'allowFullscreen', 'true' );
|
||||
iframe.setAttribute( 'src', videoUrl );
|
||||
|
||||
videoWrapper.classList.remove( 'loading' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the iframe video with autoplay enabled.
|
||||
*
|
||||
* @param {HTMLElement} videoWrapper - The video wrapper element.
|
||||
*/
|
||||
loadIframeVideoWithAutoPlay( videoWrapper ) {
|
||||
this.loadIframeVideo( videoWrapper, true );
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize LazyLoadVideo
|
||||
new LazyLoadVideo();
|
||||
} )();
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Source: https://developers.google.com/speed/webp/faq#in_your_own_javascript.
|
||||
function check_feature(feature, callback) {
|
||||
const testImages = {
|
||||
webp: "data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
|
||||
avif: "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A="
|
||||
};
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
const result = (img.width > 0) && (img.height > 0);
|
||||
callback(result);
|
||||
};
|
||||
img.onerror = function () {
|
||||
callback(false);
|
||||
};
|
||||
img.src = testImages[feature];
|
||||
}
|
||||
|
||||
function make_callback(fallbackAttributeName, dataValueName, extension) {
|
||||
return function (isNextGenSupported) {
|
||||
document.documentElement.classList.add(isNextGenSupported ? extension : 'no-' + extension);
|
||||
if (isNextGenSupported) {
|
||||
return;
|
||||
}
|
||||
|
||||
const originalGetAttribute = Object.getOwnPropertyDescriptor(Element.prototype, 'getAttribute');
|
||||
|
||||
// Redefine the getAttribute function with a custom implementation
|
||||
Object.defineProperty(Element.prototype, 'getAttribute', {
|
||||
value: function (attributeName) {
|
||||
if (!this.dataset.hasOwnProperty(dataValueName)) {
|
||||
return originalGetAttribute.value.call(this, attributeName);
|
||||
}
|
||||
|
||||
const fallbackObject = JSON.parse(this.dataset[dataValueName]);
|
||||
|
||||
if (attributeName in fallbackObject) {
|
||||
return fallbackObject[attributeName];
|
||||
}
|
||||
|
||||
return originalGetAttribute.value.call(this, attributeName);
|
||||
}
|
||||
});
|
||||
|
||||
const elementsWithFallback = document.querySelectorAll('[' + fallbackAttributeName + ']:not(.lazyload)');
|
||||
if (elementsWithFallback.length) {
|
||||
// Update background image, src, srcset.
|
||||
const imageDisplayAttrs = ['src', 'srcset'];
|
||||
elementsWithFallback.forEach((element) => {
|
||||
const fallbackObject = JSON.parse(element.dataset[dataValueName]);
|
||||
imageDisplayAttrs.forEach(function (attrName) {
|
||||
if (attrName in fallbackObject) {
|
||||
element.setAttribute(attrName, fallbackObject[attrName]);
|
||||
}
|
||||
});
|
||||
|
||||
// Update background image.
|
||||
if ('bg' in fallbackObject) {
|
||||
element.style.background = fallbackObject.bg;
|
||||
}
|
||||
if ('bg-image' in fallbackObject) {
|
||||
element.style.backgroundImage = fallbackObject['bg-image'];
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (wp_smushit_nextgen_data?.mode === 'avif') {
|
||||
check_feature('avif', make_callback('data-smush-avif-fallback', 'smushAvifFallback', 'avif'));
|
||||
} else {
|
||||
check_feature('webp', make_callback('data-smush-webp-fallback', 'smushWebpFallback', 'webp'));
|
||||
}
|
||||
})();
|
||||
+437
@@ -0,0 +1,437 @@
|
||||
import '../../scss/resize-detection.scss';
|
||||
|
||||
/**
|
||||
* Image resize detection (IRD).
|
||||
*
|
||||
* Show all wrongly scaled images with a highlighted border and resize box.
|
||||
*
|
||||
* Made in pure JS.
|
||||
* DO NOT ADD JQUERY SUPPORT!!!
|
||||
*
|
||||
* @since 2.9
|
||||
*/
|
||||
( function() {
|
||||
'use strict';
|
||||
|
||||
const SmushIRS = {
|
||||
bar: document.getElementById( 'smush-image-bar' ),
|
||||
toggle: document.getElementById( 'smush-image-bar-toggle' ),
|
||||
images: {
|
||||
bigger: [],
|
||||
smaller: [],
|
||||
},
|
||||
strings: window.wp_smush_resize_vars,
|
||||
|
||||
/**
|
||||
* Init scripts.
|
||||
*/
|
||||
init() {
|
||||
/**
|
||||
* Make sure these are set, before we proceed.
|
||||
*/
|
||||
if ( ! this.bar ) {
|
||||
this.bar = document.getElementById( 'smush-image-bar' );
|
||||
}
|
||||
if ( ! this.toggle ) {
|
||||
this.toggle = document.getElementById(
|
||||
'smush-image-bar-toggle'
|
||||
);
|
||||
}
|
||||
|
||||
this.process();
|
||||
|
||||
// Register the event handler after everything is done.
|
||||
this.toggle.addEventListener(
|
||||
'click',
|
||||
this.handleToggleClick.bind( this )
|
||||
);
|
||||
|
||||
this.initImageVisibilityObserver();
|
||||
},
|
||||
|
||||
/**
|
||||
* Do image processing.
|
||||
*/
|
||||
process() {
|
||||
const icon = this.toggle.querySelector( 'i' );
|
||||
|
||||
icon.classList.add( 'sui-icon-loader' );
|
||||
icon.classList.remove( 'sui-icon-info' );
|
||||
|
||||
this.detectImages();
|
||||
|
||||
if ( ! this.images.bigger.length && ! this.images.smaller.length ) {
|
||||
this.toggle.classList.add( 'smush-toggle-success' );
|
||||
document.getElementById(
|
||||
'smush-image-bar-notice'
|
||||
).style.display = 'block';
|
||||
document.getElementById(
|
||||
'smush-image-bar-notice-desc'
|
||||
).style.display = 'none';
|
||||
} else {
|
||||
this.toggle.classList.remove( 'smush-toggle-success' );
|
||||
document.getElementById(
|
||||
'smush-image-bar-notice'
|
||||
).style.display = 'none';
|
||||
document.getElementById(
|
||||
'smush-image-bar-notice-desc'
|
||||
).style.display = 'block';
|
||||
this.generateMarkup( 'bigger' );
|
||||
this.generateMarkup( 'smaller' );
|
||||
}
|
||||
|
||||
this.toggleDivs();
|
||||
|
||||
icon.classList.remove( 'sui-icon-loader' );
|
||||
icon.classList.add( 'sui-icon-info' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Various checks to see if the image should be processed.
|
||||
*
|
||||
* @param {Object} image
|
||||
* @return {boolean} Should skip image or not.
|
||||
*/
|
||||
shouldSkipImage( image ) {
|
||||
if ( this.isIgnoredImage( image ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip 1x1px and 0x0px images.
|
||||
if (
|
||||
image.clientWidth === image.clientHeight &&
|
||||
( 1 === image.clientWidth || 0 === image.clientWidth )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip 1x1px and 0x0px placeholders.
|
||||
if ( this.isPlaceholder( image ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If width attribute is not set, do not continue.
|
||||
return null === image.clientWidth || null === image.clientHeight;
|
||||
},
|
||||
|
||||
isIgnoredImage( image ) {
|
||||
// Skip avatars.
|
||||
if ( image.classList.contains( 'avatar' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip images from Smush CDN with auto-resize feature.
|
||||
return 'string' === typeof image.getAttribute( 'no-resize-detection' );
|
||||
},
|
||||
|
||||
isPlaceholder( image ) {
|
||||
return image.naturalWidth === image.naturalHeight && image.naturalWidth < 32;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get tooltip text.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {string} Tooltip.
|
||||
*/
|
||||
getTooltipText( props ) {
|
||||
let tooltipText = '';
|
||||
|
||||
if ( props.bigger_width || props.bigger_height ) {
|
||||
/** @param {string} strings.large_image */
|
||||
tooltipText = this.strings.large_image;
|
||||
} else if ( props.smaller_width || props.smaller_height ) {
|
||||
/** @param {string} strings.small_image */
|
||||
tooltipText = this.strings.small_image;
|
||||
}
|
||||
|
||||
return tooltipText
|
||||
.replace( 'width', props.real_width )
|
||||
.replace( 'height', props.real_height );
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate markup.
|
||||
*
|
||||
* @param {string} type Accepts: 'bigger' or 'smaller'.
|
||||
*/
|
||||
generateMarkup( type ) {
|
||||
this.images[ type ].forEach( ( image, index ) => {
|
||||
const item = document.createElement( 'div' ),
|
||||
tooltip = this.getTooltipText( image.props );
|
||||
|
||||
item.setAttribute(
|
||||
'class',
|
||||
'smush-resize-box smush-tooltip smush-tooltip-constrained'
|
||||
);
|
||||
item.setAttribute( 'data-tooltip', tooltip );
|
||||
item.setAttribute( 'data-image', image.class );
|
||||
item.addEventListener( 'click', ( e ) =>
|
||||
this.highlightImage( e )
|
||||
);
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="smush-image-info">
|
||||
<span>${ index + 1 }</span>
|
||||
<span class="smush-tag">${ image.props.computed_width } x ${ image.props.computed_height }px</span>
|
||||
<i class="smush-front-icons smush-front-icon-arrows-in" aria-hidden="true"> </i>
|
||||
<span class="smush-tag smush-tag-success">${ image.props.real_width } × ${ image.props.real_height }px</span>
|
||||
</div>
|
||||
<div class="smush-image-description">${ tooltip }</div>
|
||||
`;
|
||||
|
||||
document
|
||||
.getElementById( 'smush-image-bar-items-' + type )
|
||||
.appendChild( item );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Show/hide sections based on images.
|
||||
*/
|
||||
toggleDivs() {
|
||||
const types = [ 'bigger', 'smaller' ];
|
||||
types.forEach( ( type ) => {
|
||||
const div = document.getElementById(
|
||||
'smush-image-bar-items-' + type
|
||||
);
|
||||
if ( 0 === this.images[ type ].length ) {
|
||||
div.style.display = 'none';
|
||||
} else {
|
||||
div.style.display = 'block';
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Scroll the selected image into view and highlight it.
|
||||
*
|
||||
* @param {Object} e
|
||||
*/
|
||||
highlightImage( e ) {
|
||||
this.removeSelection();
|
||||
|
||||
const el = document.getElementsByClassName(
|
||||
e.currentTarget.dataset.image
|
||||
);
|
||||
if ( 'undefined' !== typeof el[ 0 ] ) {
|
||||
// Display description box.
|
||||
e.currentTarget.classList.toggle( 'show-description' );
|
||||
|
||||
// Scroll and flash image.
|
||||
el[ 0 ].scrollIntoView( {
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest',
|
||||
} );
|
||||
el[ 0 ].style.opacity = '0.5';
|
||||
setTimeout( () => {
|
||||
el[ 0 ].style.opacity = '1';
|
||||
}, 1000 );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle click on the toggle item.
|
||||
*/
|
||||
handleToggleClick() {
|
||||
this.bar.classList.toggle( 'closed' );
|
||||
this.toggle.classList.toggle( 'closed' );
|
||||
this.removeSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove selected items.
|
||||
*/
|
||||
removeSelection() {
|
||||
const items = document.getElementsByClassName( 'show-description' );
|
||||
if ( items.length > 0 ) {
|
||||
Array.from( items ).forEach( ( div ) =>
|
||||
div.classList.remove( 'show-description' )
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Function to highlight all scaled images.
|
||||
*
|
||||
* Add yellow border and then show one small box to
|
||||
* resize the images as per the required size, on fly.
|
||||
*/
|
||||
detectImages() {
|
||||
const images = document.getElementsByTagName( 'img' );
|
||||
for ( const image of images ) {
|
||||
if ( this.shouldSkipImage( image ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get defined width and height.
|
||||
const props = {
|
||||
real_width: image.clientWidth,
|
||||
real_height: image.clientHeight,
|
||||
computed_width: image.naturalWidth,
|
||||
computed_height: image.naturalHeight,
|
||||
bigger_width: image.clientWidth * 1.5 < image.naturalWidth,
|
||||
bigger_height:
|
||||
image.clientHeight * 1.5 < image.naturalHeight,
|
||||
smaller_width: image.clientWidth > image.naturalWidth,
|
||||
smaller_height: image.clientHeight > image.naturalHeight,
|
||||
};
|
||||
|
||||
// In case image is in correct size, do not continue.
|
||||
if (
|
||||
! props.bigger_width &&
|
||||
! props.bigger_height &&
|
||||
! props.smaller_width &&
|
||||
! props.smaller_height
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const imgType =
|
||||
props.bigger_width || props.bigger_height
|
||||
? 'bigger'
|
||||
: 'smaller',
|
||||
imageClass =
|
||||
`smush-image-${ imgType }-${ this.images[ imgType ].length + 1 }`;
|
||||
|
||||
// Fill the images arrays.
|
||||
this.images[ imgType ].push( {
|
||||
src: image,
|
||||
props,
|
||||
class: imageClass,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Add class to original image.
|
||||
* Can't add two classes in single add(), because no support in IE11.
|
||||
* image.classList.add('smush-detected-img', imageClass);
|
||||
*/
|
||||
image.classList.add( 'smush-detected-img' );
|
||||
image.classList.add( imageClass );
|
||||
}
|
||||
}, // End detectImages()
|
||||
|
||||
/**
|
||||
* Allows refreshing the list. A good way is to refresh on lazyload actions.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
refresh() {
|
||||
// Clear out classes on DOM.
|
||||
for ( let id in this.images.bigger ) {
|
||||
if ( this.images.bigger.hasOwnProperty( id ) ) {
|
||||
this.images.bigger[ id ].src.classList.remove(
|
||||
'smush-detected-img'
|
||||
);
|
||||
this.images.bigger[ id ].src.classList.remove(
|
||||
'smush-image-bigger-' + ( ++id )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for ( let id in this.images.smaller ) {
|
||||
if ( this.images.smaller.hasOwnProperty( id ) ) {
|
||||
this.images.smaller[ id ].src.classList.remove(
|
||||
'smush-detected-img'
|
||||
);
|
||||
this.images.smaller[ id ].src.classList.remove(
|
||||
'smush-image-smaller-' + ( ++id )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.images = {
|
||||
bigger: [],
|
||||
smaller: [],
|
||||
};
|
||||
|
||||
// This might be overkill - there will probably never be a situation when there are less images than on
|
||||
// initial page load.
|
||||
const elements = document.getElementsByClassName(
|
||||
'smush-resize-box'
|
||||
);
|
||||
while ( elements.length > 0 ) {
|
||||
elements[ 0 ].remove();
|
||||
}
|
||||
|
||||
this.process();
|
||||
},
|
||||
|
||||
/**
|
||||
* This will monitor all images on the page and detect their sizes once they’ve loaded.
|
||||
*
|
||||
* @since 3.18.0
|
||||
*/
|
||||
initImageVisibilityObserver() {
|
||||
let imagesSelector = 'img:not(.smush-detected-img)';
|
||||
const smushLazyloadEnabled = typeof window.lazySizes !== 'undefined';
|
||||
if ( smushLazyloadEnabled ) {
|
||||
const lazyLoadClassName = window.lazySizes?.cfg?.lazyClass || 'lazyload';
|
||||
imagesSelector += `:not(.${ lazyLoadClassName } )`;
|
||||
}
|
||||
const lazyImages = document.querySelectorAll( imagesSelector );
|
||||
|
||||
if ( ! lazyImages.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = {
|
||||
threshold: 0.1
|
||||
};
|
||||
|
||||
let refreshTimeout = null;
|
||||
|
||||
const debouncedRefresh = () => {
|
||||
if ( refreshTimeout ) {
|
||||
clearTimeout( refreshTimeout );
|
||||
}
|
||||
refreshTimeout = setTimeout( () => {
|
||||
this.refresh();
|
||||
refreshTimeout = null;
|
||||
}, 500 );
|
||||
};
|
||||
|
||||
const imageObserver = new IntersectionObserver( ( entries ) => {
|
||||
entries.forEach( ( entry ) => {
|
||||
if ( entry.isIntersecting ) {
|
||||
const img = entry.target;
|
||||
|
||||
if ( ! img.classList.contains( 'smush-detected-img' ) ) {
|
||||
if ( img.complete && ! this.isPlaceholder( img ) ) {
|
||||
debouncedRefresh();
|
||||
} else {
|
||||
img.addEventListener( 'load', () => {
|
||||
debouncedRefresh();
|
||||
}, { once: true } );
|
||||
}
|
||||
|
||||
imageObserver.unobserve( img );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}, config );
|
||||
|
||||
lazyImages.forEach( ( img ) => {
|
||||
if ( this.isIgnoredImage( img ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
imageObserver.observe( img );
|
||||
} );
|
||||
},
|
||||
|
||||
}; // End WP_Smush_IRS
|
||||
|
||||
/**
|
||||
* After page load, initialize toggle event.
|
||||
*/
|
||||
window.addEventListener( 'DOMContentLoaded', () => SmushIRS.init() );
|
||||
window.addEventListener( 'lazyloaded', ( event ) => {
|
||||
if ( 'IMG' !== event?.target?.tagName ) {
|
||||
return;
|
||||
}
|
||||
|
||||
SmushIRS.refresh();
|
||||
} );
|
||||
}() );
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import tracker from './utils/tracker';
|
||||
|
||||
export default class GlobalTracking {
|
||||
init() {
|
||||
this.trackSubmenuProUpsell();
|
||||
this.trackPluginListProUpsell();
|
||||
this.trackDashboardWidgetProUpsell();
|
||||
}
|
||||
|
||||
trackSubmenuProUpsell() {
|
||||
const submenuUpgradeLink = document.querySelector( '#toplevel_page_smush a[href*="utm_campaign=smush_submenu_upsell' );
|
||||
if ( submenuUpgradeLink ) {
|
||||
submenuUpgradeLink.addEventListener( 'click', ( event ) => {
|
||||
this.trackGeneralProUpsell( 'submenu', event?.target?.href );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
trackPluginListProUpsell() {
|
||||
const pluginlistUpgradeLink = document.getElementById( 'smush-pluginlist-upgrade-link' );
|
||||
if ( pluginlistUpgradeLink ) {
|
||||
pluginlistUpgradeLink.addEventListener( 'click', ( event ) => {
|
||||
this.trackGeneralProUpsell( 'plugins_list', event?.target?.href );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
trackDashboardWidgetProUpsell() {
|
||||
const upsellBox = document.getElementById( 'smush-box-dashboard-upsell-upsell' );
|
||||
if ( ! upsellBox ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dashboardProUpsellLink = upsellBox.querySelector( 'a[href*=smush-dashboard-upsell]' );
|
||||
if ( dashboardProUpsellLink ) {
|
||||
dashboardProUpsellLink.addEventListener( 'click', ( event ) => {
|
||||
this.trackGeneralProUpsell( 'dash_widget', event?.target?.href );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
trackSetupWizardProUpsell( utmLink, proInterests ) {
|
||||
this.trackGeneralProUpsell( 'wizard', utmLink, proInterests );
|
||||
}
|
||||
|
||||
trackGeneralProUpsell( localtion, utmLink, proInterests = 'na' ) {
|
||||
this.trackProUpsell( {
|
||||
Feature: 'pro_general',
|
||||
Location: localtion,
|
||||
'UTM Link': utmLink,
|
||||
'Pro Interests': proInterests,
|
||||
} );
|
||||
}
|
||||
|
||||
trackProUpsell( properties ) {
|
||||
properties = Object.assign( {
|
||||
'User Action': 'cta_clicked',
|
||||
}, properties );
|
||||
|
||||
tracker.track( 'smush_pro_upsell', properties );
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import '../scss/common.scss';
|
||||
import DeactivationSurvey from './modules/deactivation-survey';
|
||||
import GlobalTracking from './global-tracking';
|
||||
|
||||
/* global ajaxurl */
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', function() {
|
||||
// Deactivation survey modal.
|
||||
( new DeactivationSurvey() ).init();
|
||||
// Global Trackings.
|
||||
( new GlobalTracking() ).init();
|
||||
|
||||
// Dismiss notices.
|
||||
const dismissNoticeButton = document.querySelectorAll(
|
||||
'.smush-dismissible-notice .smush-dismiss-notice-button'
|
||||
);
|
||||
dismissNoticeButton.forEach((button) => {
|
||||
button.addEventListener('click', handleDismissNotice);
|
||||
});
|
||||
|
||||
function handleDismissNotice(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const button = event.target;
|
||||
const notice = button.closest('.smush-dismissible-notice');
|
||||
const key = notice.getAttribute('data-key');
|
||||
|
||||
dismissNotice( key, notice );
|
||||
}
|
||||
|
||||
function dismissNotice( key, notice ) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
'POST',
|
||||
ajaxurl + '?action=smush_dismiss_notice&key=' + key + '&_ajax_nonce=' + smush_global.nonce,
|
||||
true
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if (notice) {
|
||||
// Trigger WordPress notice dismissal.
|
||||
const noticeDismissButton = notice.querySelector('button.notice-dismiss');
|
||||
if (noticeDismissButton) {
|
||||
noticeDismissButton.dispatchEvent(new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
} else {
|
||||
notice.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
const dismissCacheNoticeButton = document.querySelector( '#wp-smush-cache-notice .smush-dismiss-notice-button' );
|
||||
if ( dismissCacheNoticeButton ) {
|
||||
dismissCacheNoticeButton.addEventListener( 'click', function() {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
'POST',
|
||||
ajaxurl + '?action=smush_dismiss_cache_notice&_ajax_nonce=' + smush_global.nonce,
|
||||
true
|
||||
);
|
||||
xhr.onload = () => {
|
||||
window.SUI.closeNotice( 'wp-smush-cache-notice' );
|
||||
};
|
||||
xhr.send();
|
||||
} );
|
||||
}
|
||||
|
||||
// Show header notices.
|
||||
const handleHeaderNotice = () => {
|
||||
const headerNotice = document.querySelector('.wp-smush-dismissible-header-notice');
|
||||
if ( ! headerNotice || ! headerNotice.id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { dismissKey, message } = headerNotice.dataset;
|
||||
if ( ! message ) {
|
||||
return;
|
||||
}
|
||||
|
||||
headerNotice.onclick = (e) => {
|
||||
const classList = e.target.classList;
|
||||
const isCloseAndDismissLink = classList && classList.contains( 'smush-close-and-dismiss-notice' );
|
||||
const shouldDismissNotice = classList && ( isCloseAndDismissLink || classList.contains('sui-icon-check') || classList.contains('sui-button-icon') );
|
||||
if ( ! shouldDismissNotice ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( dismissKey ) {
|
||||
dismissNotice( dismissKey );
|
||||
}
|
||||
|
||||
if ( isCloseAndDismissLink ) {
|
||||
window.SUI.closeNotice( headerNotice.id );
|
||||
}
|
||||
}
|
||||
|
||||
const noticeOptions = {
|
||||
type: 'warning',
|
||||
icon: 'info',
|
||||
dismiss: {
|
||||
show: true,
|
||||
label: wp_smush_msgs.noticeDismiss,
|
||||
tooltip: wp_smush_msgs.noticeDismissTooltip,
|
||||
},
|
||||
};
|
||||
|
||||
window.SUI.openNotice(
|
||||
headerNotice.id,
|
||||
message,
|
||||
noticeOptions
|
||||
);
|
||||
}
|
||||
|
||||
handleHeaderNotice();
|
||||
});
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import Fetcher from './utils/fetcher';
|
||||
|
||||
class LoopbackTester {
|
||||
delayTimeOnFailure = 5000;
|
||||
|
||||
performTest() {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
this.startTest().then( ( res ) => {
|
||||
if ( res?.success ) {
|
||||
this.getResult(
|
||||
resolve,
|
||||
() => {
|
||||
setTimeout( () => {
|
||||
this.getResult( resolve, reject, reject );
|
||||
}, this.delayTimeOnFailure );
|
||||
},
|
||||
reject
|
||||
);
|
||||
} else {
|
||||
reject( res );
|
||||
}
|
||||
} ).catch( ( error ) => {
|
||||
reject( error );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
startTest() {
|
||||
return Fetcher.background.backgroundHealthyCheck();
|
||||
}
|
||||
|
||||
getResult( successCallback, failedCallback, errorCallback ) {
|
||||
return this.fetchResult().then( ( status ) => {
|
||||
let data = status?.data;
|
||||
if (status?.success && data?.loopback) {
|
||||
successCallback(data);
|
||||
} else {
|
||||
failedCallback(status);
|
||||
}
|
||||
} ).catch( ( error ) => {
|
||||
errorCallback( error );
|
||||
} );
|
||||
}
|
||||
|
||||
fetchResult() {
|
||||
return Fetcher.background.backgroundHealthyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
export default ( new LoopbackTester() );
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
jQuery(function ($) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Handle the Smush Stats link click
|
||||
*/
|
||||
$( 'body' ).on( 'click', 'a.smush-stats-details', function ( e ) {
|
||||
// If disabled
|
||||
if ( $( this ).prop( 'disabled' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const $link = $( this );
|
||||
const $wrapper = $link.parents().eq( 1 ).find( '.smush-stats-wrapper' );
|
||||
|
||||
// Toggle expanded state
|
||||
$link.toggleClass( 'smush-stats-expanded' );
|
||||
$wrapper.slideToggle();
|
||||
} );
|
||||
});
|
||||
+1041
File diff suppressed because it is too large
Load Diff
+472
@@ -0,0 +1,472 @@
|
||||
/* global WP_Smush */
|
||||
|
||||
/**
|
||||
* Bulk Smush Background Optimization.
|
||||
*
|
||||
* @since 3.12.0
|
||||
*/
|
||||
import Fetcher from '../utils/fetcher';
|
||||
import tracker from '../utils/tracker';
|
||||
import SmushProgress from '../common/progressbar';
|
||||
import {GlobalStats, UpsellManger} from "../common/globalStats";
|
||||
import loopbackTester from '../loopback-tester';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
if (!window.wp_smush_msgs) {
|
||||
return;
|
||||
}
|
||||
const $ = document.querySelector.bind(document);
|
||||
const NO_FREE_LIMITED = 50;
|
||||
|
||||
/**
|
||||
* Handle Background Process.
|
||||
* @returns
|
||||
*/
|
||||
const BackgroundProcess = () => {
|
||||
return {
|
||||
handle(action) {
|
||||
return Fetcher.background[action]();
|
||||
},
|
||||
initState() {
|
||||
return Fetcher.background.initState();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Optimization.
|
||||
*/
|
||||
const BackgroundSmush = (() => {
|
||||
|
||||
const startBtn = window.wp_smushit_data && window.wp_smushit_data.bo_stats && $('.wp-smush-bo-start');
|
||||
if (!startBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const BO = new BackgroundProcess();
|
||||
const bulkWrapper = $('.bulk-smush-wrapper');
|
||||
const reScanImagesButton = $('.wp-smush-scan');
|
||||
let intervalHandle = 0;
|
||||
let cancellationInProgress = false;
|
||||
return {
|
||||
hookStatusChecks() {
|
||||
if (intervalHandle) {
|
||||
// Already done
|
||||
return;
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
let statusSyncInProgress = false;
|
||||
let statSyncInProgress = false;
|
||||
intervalHandle = setInterval(() => {
|
||||
if (statusSyncInProgress) {
|
||||
return;
|
||||
}
|
||||
statusSyncInProgress = true;
|
||||
|
||||
count++;
|
||||
const statusSynced = this.syncBackgroundStatus();
|
||||
if (count % 3 === 0) {
|
||||
// Do a full update every nth time
|
||||
statusSynced.then(() => {
|
||||
if (!statSyncInProgress) {
|
||||
this.syncStats().then(() => {
|
||||
statSyncInProgress = false;
|
||||
});
|
||||
statSyncInProgress = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
statusSynced.finally(() => {
|
||||
statusSyncInProgress = false;
|
||||
});
|
||||
}, 3 * 1000);
|
||||
},
|
||||
resetBOStatsOnStart() {
|
||||
GlobalStats.setBoStats( {
|
||||
is_cancelled: false,
|
||||
is_completed: false,
|
||||
processed_items: 0,
|
||||
failed_items: 0,
|
||||
} );
|
||||
},
|
||||
start() {
|
||||
// Reset boStats.
|
||||
this.resetBOStatsOnStart();
|
||||
|
||||
// Disable UI.
|
||||
this.onStart();
|
||||
|
||||
loopbackTester.performTest().then( ( res ) => {
|
||||
const isLoopbackHealthy = res?.loopback;
|
||||
if ( isLoopbackHealthy ) {
|
||||
// Start BO.
|
||||
this.startBulkSmush();
|
||||
} else {
|
||||
this.showLoopbackErrorModal();
|
||||
this.onStartFailure();
|
||||
}
|
||||
} ).catch( ( error ) => {
|
||||
console.error( 'Error:', error );
|
||||
this.showLoopbackErrorModal();
|
||||
this.onStartFailure();
|
||||
} );
|
||||
},
|
||||
startBulkSmush() {
|
||||
BO.handle('start').then((res) => {
|
||||
if (res.success) {
|
||||
// Update stats.
|
||||
const updatedStats = this.updateStats(res.data, false);
|
||||
// Show progress bar.
|
||||
this.showProgressBar();
|
||||
this.hookStatusChecks();
|
||||
if ( updatedStats ) {
|
||||
// Render stats.
|
||||
GlobalStats.renderStats();
|
||||
}
|
||||
} else {
|
||||
this.showFailureNotice( res );
|
||||
this.onStartFailure( res );
|
||||
}
|
||||
});
|
||||
},
|
||||
showFailureNotice( res ) {
|
||||
WP_Smush.helpers.showNotice( res, {
|
||||
'showdismiss': true,
|
||||
'autoclose' : false,
|
||||
} );
|
||||
},
|
||||
onStartFailure( res ) {
|
||||
this.cancelBulk();
|
||||
},
|
||||
showLoopbackErrorModal() {
|
||||
const loopbackErrorModal = document.getElementById( 'smush-loopback-error-dialog' );
|
||||
if ( ! loopbackErrorModal || ! window.SUI ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache current process type.
|
||||
loopbackErrorModal.dataset.processType = 'smush';
|
||||
|
||||
WP_Smush.helpers.showModal( loopbackErrorModal.id );
|
||||
},
|
||||
/**
|
||||
* Initial state when load the Bulk Smush page while BO is running.
|
||||
*/
|
||||
initState() {
|
||||
if (!GlobalStats.getBoStats().in_processing) {
|
||||
return;
|
||||
}
|
||||
// Disable UI.
|
||||
this.onStart();
|
||||
// Start BO.
|
||||
BO.initState().then((res) => {
|
||||
if (res.success) {
|
||||
// Update stats.
|
||||
this.updateStats(res.data, false);
|
||||
// Show progress bar.
|
||||
this.showProgressBar();
|
||||
this.hookStatusChecks();
|
||||
// Maybe update errors.
|
||||
if ( res.data.errors && ! Object.keys( GlobalStats.getErrors() ).length ) {
|
||||
GlobalStats.setErrors( res.data.errors );
|
||||
}
|
||||
// Render stats.
|
||||
GlobalStats.renderStats();
|
||||
} else {
|
||||
WP_Smush.helpers.showNotice( res );
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Cancel.
|
||||
*/
|
||||
cancel() {
|
||||
cancellationInProgress = true;
|
||||
this.setCancelButtonStateToStarted();
|
||||
BO.handle('cancel').then((res) => {
|
||||
if (res.success) {
|
||||
this.cancelBulk();
|
||||
} else {
|
||||
WP_Smush.helpers.showNotice( res );
|
||||
}
|
||||
});
|
||||
},
|
||||
hideProgressBar() {
|
||||
// Hide progress bar.
|
||||
SmushProgress.close().update(0, GlobalStats.getBoStats().total_items);
|
||||
},
|
||||
showProgressBar() {
|
||||
// Reset progress bar.
|
||||
SmushProgress.update(GlobalStats.getBoStats().processed_items, GlobalStats.getBoStats().total_items);
|
||||
// Show progress bar.
|
||||
SmushProgress.show();
|
||||
},
|
||||
/**
|
||||
* Update stats.
|
||||
* @param {Object} newStats Included increment stats and new BO stats.
|
||||
* @param updateGlobal
|
||||
*/
|
||||
updateStats(newStats, updateGlobal) {
|
||||
// Make sure we have processed_stats/errors property (not added by default when start).
|
||||
newStats.global_stats = newStats.global_stats || {};
|
||||
newStats.errors = newStats.errors || {};
|
||||
const {
|
||||
global_stats,
|
||||
errors,
|
||||
...newBoStats
|
||||
} = newStats;
|
||||
if ( ! GlobalStats.isChangedStats( newBoStats ) ) {
|
||||
return false;
|
||||
}
|
||||
// Update BO stats.
|
||||
GlobalStats.setBoStats(newBoStats);
|
||||
if (updateGlobal) {
|
||||
// Update global stats.
|
||||
GlobalStats.setGlobalStats(global_stats);
|
||||
}
|
||||
// Update Errors.
|
||||
GlobalStats.setErrors( errors );
|
||||
return true;
|
||||
},
|
||||
cancelBulk() {
|
||||
// Sync Stats.
|
||||
this.syncStats(() => {
|
||||
if (100 === GlobalStats.getGlobalStats().percent_optimized) {
|
||||
// If the last item was getting processed when the user cancelled then the process might have completed
|
||||
GlobalStats.setBoStats( {
|
||||
is_completed: true
|
||||
} );
|
||||
this.onCompletedBulk();
|
||||
} else {
|
||||
// Update status of boStats.
|
||||
GlobalStats.setBoStats( {
|
||||
is_cancelled: true
|
||||
} );
|
||||
// Reset and hide progress bar.
|
||||
this.onFinish();
|
||||
// Bulk is cancelled, show bulk desc.
|
||||
SmushProgress.showBulkSmushDescription();
|
||||
}
|
||||
|
||||
cancellationInProgress = false;
|
||||
});
|
||||
},
|
||||
showCompletedMessage() {
|
||||
// Render completed message.
|
||||
// Show completed message.
|
||||
const processedWrapper = bulkWrapper.querySelector('.wp-smush-all-done');
|
||||
if ( GlobalStats.getBoStats().failed_items ) {
|
||||
let completeMessage = wp_smush_msgs.all_failed;
|
||||
if ( ! this.isFailedAllItems() ) {
|
||||
completeMessage = wp_smush_msgs.error_in_bulk.replace( '{{smushed}}', GlobalStats.getBoStats().total_items - GlobalStats.getBoStats().failed_items )
|
||||
.replace('{{total}}', GlobalStats.getBoStats().total_items )
|
||||
.replace('{{errors}}', GlobalStats.getBoStats().failed_items );
|
||||
}
|
||||
processedWrapper.querySelector('p').innerHTML = completeMessage;
|
||||
processedWrapper.classList.remove('sui-notice-success', 'sui-notice-warning');
|
||||
const noticeType = this.getNoticeType();
|
||||
const noticeIcon = 'warning' === noticeType ? 'info' : 'check-tick';
|
||||
const iconElement = processedWrapper.querySelector('.sui-notice-icon');
|
||||
processedWrapper.classList.add( 'sui-notice-' + noticeType );
|
||||
iconElement.classList.remove('sui-icon-check-tick', 'sui-icon-info');
|
||||
iconElement.classList.add( 'sui-icon-' + noticeIcon );
|
||||
} else {
|
||||
processedWrapper.querySelector('p').innerHTML = wp_smush_msgs.all_smushed;
|
||||
}
|
||||
processedWrapper.classList.remove('sui-hidden');
|
||||
},
|
||||
isFailedAllItems() {
|
||||
return GlobalStats.getBoStats().failed_items === GlobalStats.getBoStats().total_items;
|
||||
},
|
||||
getNoticeType() {
|
||||
return this.isFailedAllItems() ? 'warning' : 'success';
|
||||
},
|
||||
onCompletedBulk() {
|
||||
// Reset and hide progress bar.
|
||||
this.onFinish();
|
||||
// Bulk is completed, hide bulk desc.
|
||||
SmushProgress.hideBulkSmushDescription();
|
||||
// Show completed message.
|
||||
this.showCompletedMessage();
|
||||
|
||||
// Reset the progress when we finish so the next smushing starts from zero.
|
||||
SmushProgress.update(0, GlobalStats.getBoStats().total_items);
|
||||
},
|
||||
completeBulk() {
|
||||
// Sync Stats.
|
||||
this.syncStats(() => this.onCompletedBulk());
|
||||
},
|
||||
syncStats(onComplete = () => false) {
|
||||
return BO.handle('getStats').then((res) => {
|
||||
if ( res.success ) {
|
||||
const responseErrors = res.data.errors || {};
|
||||
this.updateStats( { global_stats: res.data, errors: responseErrors }, true );
|
||||
GlobalStats.renderStats();
|
||||
if ( res.data.content ) {
|
||||
$('#wp-smush-bulk-content').innerHTML = res.data.content;
|
||||
}
|
||||
onComplete();
|
||||
} else {
|
||||
WP_Smush.helpers.showNotice( res );
|
||||
}
|
||||
}).catch( (error) => console.log('error', error));
|
||||
},
|
||||
syncBackgroundStatus() {
|
||||
return BO.handle('getStatus').then((res) => {
|
||||
if ((res.data || {}).in_process_notice) {
|
||||
SmushProgress.setNotice( res.data.in_process_notice );
|
||||
}
|
||||
|
||||
if ( (res.data || {}).is_process_stuck ) {
|
||||
SmushProgress.showHoldOnNotice();
|
||||
}
|
||||
|
||||
if (res.success) {
|
||||
// Update stats.
|
||||
if ( this.updateStats(res.data, false) ) {
|
||||
// Update progress bar.
|
||||
SmushProgress.update(GlobalStats.getBoStats().processed_items, GlobalStats.getBoStats().total_items);
|
||||
|
||||
if (! GlobalStats.getBoStats().is_cancelled && ! GlobalStats.getBoStats().is_completed) {
|
||||
// Render stats.
|
||||
GlobalStats.renderStats();
|
||||
}
|
||||
}
|
||||
|
||||
if (GlobalStats.getBoStats().is_cancelled && !cancellationInProgress) {
|
||||
// Cancelled on server side
|
||||
this.cancelBulk();
|
||||
} else if (GlobalStats.getBoStats().is_completed) {
|
||||
this.completeBulk();
|
||||
} else if ( GlobalStats.getBoStats().is_dead ) {
|
||||
this.onDead();
|
||||
}
|
||||
} else {
|
||||
WP_Smush.helpers.showNotice( res );
|
||||
}
|
||||
});
|
||||
},
|
||||
onStart() {
|
||||
// Disable btn.
|
||||
startBtn.setAttribute('disabled', '');
|
||||
// Disable re-check images.
|
||||
reScanImagesButton && reScanImagesButton.setAttribute('disabled', '');
|
||||
$('.wp-smush-restore').setAttribute('disabled', '');
|
||||
// Show upsell cdn.
|
||||
UpsellManger.maybeShowCDNUpsellForPreSiteOnStart();
|
||||
|
||||
this.hideBulkSmushFailedNotice();
|
||||
|
||||
this.setCancelButtonStateToInitial();
|
||||
},
|
||||
hideBulkSmushFailedNotice() {
|
||||
const bulkSmushFailedNotice = document.querySelector( '.wp-smush-inline-retry-bulk-smush-notice' );
|
||||
if ( bulkSmushFailedNotice ) {
|
||||
bulkSmushFailedNotice.parentElement.classList.add( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
onFinish() {
|
||||
// Clear interval.
|
||||
if (intervalHandle) {
|
||||
clearInterval(intervalHandle);
|
||||
intervalHandle = 0;
|
||||
}
|
||||
|
||||
// Disable btn.
|
||||
startBtn.removeAttribute('disabled');
|
||||
// Reset and hide Progress Bar.
|
||||
this.hideProgressBar();
|
||||
// Disable re-check images.
|
||||
reScanImagesButton && reScanImagesButton.removeAttribute('disabled', '');
|
||||
$('.wp-smush-restore').removeAttribute('disabled', '');
|
||||
|
||||
// Show upsell cdn.
|
||||
UpsellManger.maybeShowCDNUpsellForPreSiteOnCompleted();
|
||||
},
|
||||
onDead() {
|
||||
this.onFinish();
|
||||
SmushProgress.showBulkSmushDescription();
|
||||
this.showRetryBulkSmushModal();
|
||||
},
|
||||
showRetryBulkSmushModal() {
|
||||
const retryModalElement = document.getElementById( 'smush-retry-bulk-smush-notice' );
|
||||
if ( ! window.SUI || ! retryModalElement ) {
|
||||
return;
|
||||
}
|
||||
|
||||
retryModalElement.querySelector('.smush-retry-bulk-smush-notice-button').onclick = (e) => {
|
||||
e.preventDefault();
|
||||
window.SUI.closeModal( 'smush-retry-bulk-smush-notice' );
|
||||
this.start();
|
||||
}
|
||||
|
||||
window.SUI.openModal(
|
||||
'smush-retry-bulk-smush-notice',
|
||||
'wpbody-content',
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
},
|
||||
init() {
|
||||
if (!startBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start BO.
|
||||
startBtn.onclick = () => {
|
||||
const requiredScanMedia = startBtn.classList.contains('wp-smush-scan-and-bulk-smush');
|
||||
if ( requiredScanMedia ) {
|
||||
return;
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
|
||||
const triggerBulkSmushButton = document.querySelector( '.wp-smush-inline-retry-bulk-smush-notice .wp-smush-trigger-bulk-smush' );
|
||||
if ( triggerBulkSmushButton ) {
|
||||
triggerBulkSmushButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
|
||||
startBtn.click();
|
||||
} );
|
||||
}
|
||||
|
||||
// If BO is running, initial new state.
|
||||
this.initState();
|
||||
},
|
||||
setCancelButtonStateToInitial() {
|
||||
SmushProgress.setCancelButtonLabel( wp_smush_msgs.cancel );
|
||||
SmushProgress.setOnCancelCallback( this.cancel.bind(this) );
|
||||
},
|
||||
setCancelButtonStateToStarted() {
|
||||
SmushProgress.setCancelButtonLabel( wp_smush_msgs.cancelling );
|
||||
SmushProgress.setOnCancelCallback( () => false );
|
||||
},
|
||||
}
|
||||
})();
|
||||
// Run.
|
||||
BackgroundSmush && BackgroundSmush.init();
|
||||
/**
|
||||
* For globalStats, we will need to update it on reload and after re-checking images,
|
||||
* and on finish the BO.
|
||||
* 1. On finish, we handled via BackgroundSmush.syncStats -> BackgroundSmush.updateStats
|
||||
* 2. On reload or after re-checking images, we need to update globalStats from global variable:
|
||||
* window.wp_smushit_data
|
||||
*/
|
||||
// Update global stats after re-checking images.
|
||||
document.addEventListener('wpSmushAfterRecheckImages', function(){
|
||||
GlobalStats.updateGlobalStatsFromSmushScriptData();
|
||||
});
|
||||
|
||||
document.addEventListener('backgroundBulkSmushOnScanCompleted', function(){
|
||||
if ( ! BackgroundSmush ) {
|
||||
return;
|
||||
}
|
||||
GlobalStats.setBoStats({
|
||||
in_processing: true,
|
||||
});
|
||||
BackgroundSmush.initState();
|
||||
});
|
||||
})();
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
/* global _ */
|
||||
|
||||
import tracker from "../utils/tracker";
|
||||
|
||||
/**
|
||||
* Bulk restore JavaScript code.
|
||||
*
|
||||
* @since 3.2.2
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Bulk restore modal.
|
||||
*
|
||||
* @since 3.2.2
|
||||
*/
|
||||
WP_Smush.restore = {
|
||||
modal: document.getElementById('smush-restore-images-dialog'),
|
||||
contentContainer: document.getElementById('smush-bulk-restore-content'),
|
||||
settings: {
|
||||
slide: 'start', // start, progress or finish.
|
||||
success: 0,
|
||||
errors: [],
|
||||
},
|
||||
items: [], // total items, 1 item = 1 step.
|
||||
success: [], // successful items restored.
|
||||
errors: [], // failed items.
|
||||
currentStep: 0,
|
||||
totalSteps: 0,
|
||||
|
||||
/**
|
||||
* Init module.
|
||||
*/
|
||||
init() {
|
||||
if (!this.modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.settings = {
|
||||
slide: 'start',
|
||||
success: 0,
|
||||
errors: [],
|
||||
};
|
||||
|
||||
this.resetModalWidth();
|
||||
this.renderTemplate();
|
||||
|
||||
// Show the modal.
|
||||
|
||||
window.SUI.openModal(
|
||||
'smush-restore-images-dialog',
|
||||
'wpbody-content',
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the template, register new listeners.
|
||||
*/
|
||||
renderTemplate() {
|
||||
const template = WP_Smush.onboarding.template('smush-bulk-restore');
|
||||
const content = template(this.settings);
|
||||
|
||||
if (content) {
|
||||
this.contentContainer.innerHTML = content;
|
||||
}
|
||||
|
||||
this.bindSubmit();
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset modal width.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
resetModalWidth() {
|
||||
this.modal.style.maxWidth = '460px';
|
||||
this.modal.querySelector('.sui-box').style.maxWidth = '460px';
|
||||
},
|
||||
|
||||
/**
|
||||
* Catch "Finish setup wizard" button click.
|
||||
*/
|
||||
bindSubmit() {
|
||||
const confirmButton = this.modal.querySelector(
|
||||
'button[id="smush-bulk-restore-button"]'
|
||||
);
|
||||
const self = this;
|
||||
|
||||
if (confirmButton) {
|
||||
confirmButton.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
self.resetModalWidth();
|
||||
|
||||
self.settings = { slide: 'progress' };
|
||||
self.errors = [];
|
||||
self.success = [];
|
||||
|
||||
self.renderTemplate();
|
||||
self.initScan();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel the bulk restore.
|
||||
*/
|
||||
cancel() {
|
||||
if (
|
||||
'start' === this.settings.slide ||
|
||||
'finish' === this.settings.slide
|
||||
) {
|
||||
// Hide the modal.
|
||||
window.SUI.closeModal();
|
||||
} else {
|
||||
this.updateProgressBar(true);
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update progress bar during directory smush.
|
||||
*
|
||||
* @param {boolean} cancel Cancel status.
|
||||
*/
|
||||
updateProgressBar(cancel = false) {
|
||||
let progress = 0;
|
||||
if (0 < this.currentStep) {
|
||||
progress = Math.min(
|
||||
Math.round((this.currentStep * 100) / this.totalSteps),
|
||||
99
|
||||
);
|
||||
}
|
||||
|
||||
if (progress > 100) {
|
||||
progress = 100;
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
this.modal.querySelector('.sui-progress-text span').innerHTML =
|
||||
progress + '%';
|
||||
this.modal.querySelector('.sui-progress-bar span').style.width =
|
||||
progress + '%';
|
||||
|
||||
const statusDiv = this.modal.querySelector(
|
||||
'.sui-progress-state-text'
|
||||
);
|
||||
if (progress >= 90) {
|
||||
statusDiv.innerHTML = 'Finalizing...';
|
||||
} else if (cancel) {
|
||||
statusDiv.innerHTML = 'Cancelling...';
|
||||
} else {
|
||||
statusDiv.innerHTML =
|
||||
this.currentStep +
|
||||
'/' +
|
||||
this.totalSteps +
|
||||
' ' +
|
||||
'images restored';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* First step in bulk restore - get the bulk attachment count.
|
||||
*/
|
||||
initScan() {
|
||||
const self = this;
|
||||
const _nonce = document.getElementById('_wpnonce');
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', ajaxurl + '?action=get_image_count', true);
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if (200 === xhr.status) {
|
||||
const res = JSON.parse(xhr.response);
|
||||
if ('undefined' !== typeof res.data.items) {
|
||||
self.items = res.data.items;
|
||||
self.totalSteps = res.data.items.length;
|
||||
self.step();
|
||||
}
|
||||
} else {
|
||||
window.console.log(
|
||||
'Request failed. Returned status of ' + xhr.status
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send('_ajax_nonce=' + _nonce.value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute a scan step recursively
|
||||
*/
|
||||
step() {
|
||||
const self = this;
|
||||
const _nonce = document.getElementById('_wpnonce');
|
||||
|
||||
if (0 < this.items.length) {
|
||||
const item = this.items.pop();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', ajaxurl + '?action=restore_step', true);
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.onload = () => {
|
||||
this.currentStep++;
|
||||
|
||||
if (200 === xhr.status) {
|
||||
const res = JSON.parse(xhr.response);
|
||||
const data = ((res || {}).data || {});
|
||||
if (data.success) {
|
||||
self.success.push(item);
|
||||
} else {
|
||||
self.errors.push({
|
||||
id: item,
|
||||
src: data.src || "Error",
|
||||
thumb: data.thumb,
|
||||
link: data.link,
|
||||
error_code: data.error_code || '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.updateProgressBar();
|
||||
self.step();
|
||||
};
|
||||
xhr.send('item=' + item + '&_ajax_nonce=' + _nonce.value);
|
||||
} else {
|
||||
this.onFinish();
|
||||
|
||||
}
|
||||
},
|
||||
onFinish() {
|
||||
const missingBackupCount = this.errors.filter(
|
||||
(e) => e.error_code === 'missing_backup'
|
||||
).length;
|
||||
|
||||
const errorCopyCount = this.errors.filter(
|
||||
(e) => e.error_code === 'copy_failed'
|
||||
).length;
|
||||
|
||||
// Finish.
|
||||
this.settings = {
|
||||
slide: 'finish',
|
||||
success: this.success.length,
|
||||
errors: this.errors,
|
||||
errorsCount: this.errors.length,
|
||||
missingBackupCount: missingBackupCount,
|
||||
errorCopyCount: errorCopyCount,
|
||||
total: this.totalSteps,
|
||||
};
|
||||
|
||||
this.renderTemplate();
|
||||
if (0 < this.errors.length) {
|
||||
this.modal.style.maxWidth = '660px';
|
||||
this.modal.querySelector('.sui-box').style.maxWidth =
|
||||
'660px';
|
||||
}
|
||||
|
||||
this.trackBulkRestoredEvent();
|
||||
},
|
||||
|
||||
trackBulkRestoredEvent() {
|
||||
tracker.track(
|
||||
'Bulk Restore Triggered',
|
||||
{
|
||||
Type: 'All',
|
||||
'Total images restored': this.settings.success,
|
||||
'Total images': this.settings.total,
|
||||
'Backup not found': this.settings.missingBackupCount
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Template function (underscores based).
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
WP_Smush.restore.template = _.memoize((id) => {
|
||||
let compiled;
|
||||
const options = {
|
||||
evaluate: /<#([\s\S]+?)#>/g,
|
||||
interpolate: /{{{([\s\S]+?)}}}/g,
|
||||
escape: /{{([^}]+?)}}(?!})/g,
|
||||
variable: 'data',
|
||||
};
|
||||
|
||||
return (data) => {
|
||||
_.templateSettings = options;
|
||||
compiled =
|
||||
compiled || _.template(document.getElementById(id).innerHTML);
|
||||
return compiled(data);
|
||||
};
|
||||
});
|
||||
})();
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
|
||||
/**
|
||||
* Bulk Smush functionality.
|
||||
*
|
||||
* @since 2.9.0 Moved from admin.js
|
||||
*/
|
||||
|
||||
import Smush from '../smush/smush';
|
||||
import Fetcher from '../utils/fetcher';
|
||||
import SmushProcess from '../common/progressbar';
|
||||
|
||||
( function( $ ) {
|
||||
'use strict';
|
||||
|
||||
class WP_Smush_Bulk {
|
||||
#bulkSmushObj;
|
||||
|
||||
constructor() {
|
||||
this.onClickBulkSmushNow();
|
||||
this.onClickIgnoreImage();
|
||||
this.onClickIgnoreAllImages();
|
||||
this.onScanCompleted();
|
||||
this.resumeBulkSmushHandler();
|
||||
}
|
||||
|
||||
onClickBulkSmushNow() {
|
||||
/**
|
||||
* Handle the Bulk Smush/Bulk re-Smush button click.
|
||||
*/
|
||||
const self = this;
|
||||
$( '.wp-smush-all' ).on( 'click', function( e ) {
|
||||
const bulkSmushButton = $( this );
|
||||
if ( bulkSmushButton.hasClass( 'wp-smush-scan-and-bulk-smush' ) ) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
self.ajaxBulkSmushStart( bulkSmushButton );
|
||||
} );
|
||||
}
|
||||
|
||||
resumeBulkSmushHandler() {
|
||||
const resumeButton = document.querySelector( '.wp-smush-resume-bulk-smush' );
|
||||
if ( ! resumeButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
resumeButton.addEventListener( 'click', ( e ) => {
|
||||
if ( ! this.#bulkSmushObj ) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const isUserClick = e.clientX > 0 && e.clientY > 0 && e.isTrusted;
|
||||
if ( ! isUserClick ) {
|
||||
return;
|
||||
}
|
||||
|
||||
WP_Smush_Bulk.#resumeBulkSmush( this.#bulkSmushObj );
|
||||
} );
|
||||
}
|
||||
|
||||
ajaxBulkSmushStart( bulkSmushButton ) {
|
||||
bulkSmushButton = bulkSmushButton || $( '#wp-smush-bulk-content .wp-smush-all' );
|
||||
// Check for IDs, if there is none (unsmushed or lossless), don't call Smush function.
|
||||
/** @param {Array} wp_smushit_data.unsmushed */
|
||||
if (
|
||||
'undefined' === typeof window.wp_smushit_data ||
|
||||
( 0 === window.wp_smushit_data.unsmushed.length &&
|
||||
0 === window.wp_smushit_data.resmush.length )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// Disable re-Smush and scan button.
|
||||
// TODO: refine what is disabled.
|
||||
$(
|
||||
'.wp-resmush.wp-smush-action, .wp-smush-scan, .wp-smush-all:not(.sui-progress-close), a.wp-smush-lossy-enable, button.wp-smush-resize-enable, button#save-settings-button'
|
||||
).prop( 'disabled', true );
|
||||
|
||||
this.#bulkSmushObj = new Smush( bulkSmushButton, true );
|
||||
SmushProcess.setOnCancelCallback( () => {
|
||||
this.#bulkSmushObj.cancelAjax();
|
||||
} ).update( 0, this.#bulkSmushObj.ids.length ).show();
|
||||
|
||||
// Show upsell cdn.
|
||||
this.maybeShowCDNUpsellForPreSiteOnStart();
|
||||
|
||||
// Run bulk Smush.
|
||||
this.#bulkSmushObj.run();
|
||||
}
|
||||
|
||||
static #resumeBulkSmush( bulkSmushObj ) {
|
||||
SmushProcess.disableExceedLimitMode();
|
||||
SmushProcess.hideBulkSmushDescription();
|
||||
bulkSmushObj.onStart();
|
||||
bulkSmushObj.callAjax();
|
||||
}
|
||||
|
||||
onClickIgnoreImage() {
|
||||
/**
|
||||
* Ignore file from bulk Smush.
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
$( 'body' ).on( 'click', '.smush-ignore-image', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const self = $( this );
|
||||
|
||||
self.prop( 'disabled', true );
|
||||
self.attr( 'data-tooltip' );
|
||||
self.removeClass( 'sui-tooltip' );
|
||||
$.post( ajaxurl, {
|
||||
action: 'ignore_bulk_image',
|
||||
id: self.attr( 'data-id' ),
|
||||
_ajax_nonce: wp_smush_msgs.nonce,
|
||||
} ).done( ( response ) => {
|
||||
if ( self.is( 'a' ) && response.success && 'undefined' !== typeof response.data.html ) {
|
||||
if ( e.target.closest( '.smush-status-links' ) ) {
|
||||
self.closest( '.smush-status-links' ).parent().html( response.data.html );
|
||||
} else if ( e.target.closest( '.smush-bulk-error-row' ) ) {
|
||||
self.addClass( 'disabled' );
|
||||
e.target.closest( '.smush-bulk-error-row' ).style.opacity = 0.5;
|
||||
}
|
||||
}
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
onClickIgnoreAllImages() {
|
||||
/**
|
||||
* Ignore file from bulk Smush.
|
||||
*
|
||||
* @since 3.12.0
|
||||
*/
|
||||
const ignoreAll = document.querySelector( '.wp_smush_ignore_all_failed_items' );
|
||||
if ( ignoreAll ) {
|
||||
ignoreAll.onclick = ( e ) => {
|
||||
e.preventDefault();
|
||||
e.target.setAttribute( 'disabled', '' );
|
||||
e.target.style.cursor = 'progress';
|
||||
const type = e.target.dataset.type || null;
|
||||
e.target.classList.remove( 'sui-tooltip' );
|
||||
Fetcher.smush.ignoreAll( type ).then( ( res ) => {
|
||||
if ( res.success ) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
e.target.style.cursor = 'pointer';
|
||||
e.target.removeAttribute( 'disabled' );
|
||||
WP_Smush.helpers.showNotice( res );
|
||||
}
|
||||
} );
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
onScanCompleted() {
|
||||
document.addEventListener( 'ajaxBulkSmushOnScanCompleted', ( e ) => {
|
||||
this.ajaxBulkSmushStart();
|
||||
} );
|
||||
}
|
||||
|
||||
maybeShowCDNUpsellForPreSiteOnStart() {
|
||||
// Show upsell cdn.
|
||||
const upsell_cdn = document.querySelector( '.wp-smush-upsell-cdn' );
|
||||
if ( upsell_cdn ) {
|
||||
upsell_cdn.classList.remove( 'sui-hidden' );
|
||||
}
|
||||
}
|
||||
|
||||
isBulkSmushInProgress() {
|
||||
return this.#bulkSmushObj?.ids?.length > 0 && this.#bulkSmushObj.button?.hasClass( 'wp-smush-started' );
|
||||
}
|
||||
|
||||
getTotalEnqueuedImages() {
|
||||
return this.#bulkSmushObj?.total || 0;
|
||||
}
|
||||
|
||||
getCompletionPercentage() {
|
||||
const bulkSmushObj = this.#bulkSmushObj;
|
||||
if ( ! bulkSmushObj ) {
|
||||
return 0;
|
||||
}
|
||||
const totalEnqueuedImages = this.getTotalEnqueuedImages();
|
||||
const smushed = Number( bulkSmushObj.smushed ) || 0;
|
||||
const errors = Array.isArray( bulkSmushObj.errors ) ? bulkSmushObj.errors.length : 0;
|
||||
const processedImages = smushed + errors;
|
||||
|
||||
if ( totalEnqueuedImages > 0 ) {
|
||||
return Math.ceil( ( processedImages * 100 ) / totalEnqueuedImages );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
WP_Smush.bulk = new WP_Smush_Bulk();
|
||||
|
||||
}( jQuery ) );
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
// Deactivation survey.
|
||||
import Fetcher from '../utils/fetcher';
|
||||
import tracker from '../utils/tracker';
|
||||
|
||||
export default class DeactivationSurvey {
|
||||
constructor() {
|
||||
this.reason = 'not_set';
|
||||
this.requestedAssistance = 'na';
|
||||
this.modalAction = 'close';
|
||||
this.modalId = 'wp-smush-deactivation-survey-modal';
|
||||
this.modal = document.getElementById( this.modalId );
|
||||
}
|
||||
|
||||
init() {
|
||||
if ( ! this.modal ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleSurveyModal();
|
||||
}
|
||||
|
||||
handleSurveyModal() {
|
||||
this.deactivatePluginLink = document.querySelector( 'a[id^="deactivate-smush"]' ) || document.querySelector( 'a[id^="deactivate-wp-smushit"]' );
|
||||
if ( ! this.deactivatePluginLink ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deactivatePluginLink.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Show modal.
|
||||
this.showModal();
|
||||
|
||||
// Handle survey form.
|
||||
this.handleSurveyForm();
|
||||
} );
|
||||
}
|
||||
|
||||
handleSurveyForm() {
|
||||
this.registerRequestAssistanceLinkClickEvent();
|
||||
this.handleRadioChange();
|
||||
this.handleSkipDeactivation();
|
||||
this.handleSubmitForm();
|
||||
this.handleTrackDeactivate();
|
||||
}
|
||||
|
||||
registerRequestAssistanceLinkClickEvent() {
|
||||
const requestAssistanceLink = this.modal.querySelector( '#smush-request-assistance-link' );
|
||||
if ( requestAssistanceLink ) {
|
||||
this.requestedAssistance = 'no';
|
||||
requestAssistanceLink.addEventListener( 'click', () => {
|
||||
this.requestedAssistance = 'yes';
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
handleRadioChange() {
|
||||
const that = this;
|
||||
this.userMessageField = document.getElementById( 'smush-deactivation-user-message-field' );
|
||||
if ( ! this.userMessageField ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modal.querySelectorAll( 'input[type="radio"]' ).forEach( ( inputRadio ) => {
|
||||
inputRadio.addEventListener( 'change', function() {
|
||||
that.reason = this.value;
|
||||
that.toggleUserMessageField( this.parentElement );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
handleSkipDeactivation() {
|
||||
const skipButton = this.modal.querySelector( '.smush-skip-deactivate-button' );
|
||||
if ( ! skipButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
skipButton.addEventListener( 'click', ( e ) => {
|
||||
e.target.classList.add( 'sui-button-onload' );
|
||||
|
||||
this.modalAction = 'skip';
|
||||
// Close modal and track on closed event.
|
||||
this.closeModal();
|
||||
|
||||
// Deactivate the plugin when tracking is disabled; otherwise, handle it after tracking.
|
||||
// @see this.trackDeactivate().
|
||||
if ( ! tracker.allowToTrack() ) {
|
||||
this.redirectToDeactivateLink();
|
||||
}
|
||||
}, { once: true } );
|
||||
}
|
||||
|
||||
handleSubmitForm() {
|
||||
const submitButton = this.modal.querySelector( '.smush-submit-deactivate-button' );
|
||||
if ( ! submitButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
submitButton.addEventListener( 'click', ( e ) => {
|
||||
e.target.classList.add( 'sui-button-onload' );
|
||||
|
||||
this.modalAction = 'submit';
|
||||
// Close modal and track on closed event.
|
||||
this.closeModal();
|
||||
|
||||
// Plugin deactivation has been handled after tracking.
|
||||
// @see this.trackDeactivate().
|
||||
}, { once: true } );
|
||||
}
|
||||
|
||||
toggleUserMessageField( labelField ) {
|
||||
if ( ! this.userMessageField ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove current user message field.
|
||||
this.userMessageField.remove();
|
||||
|
||||
const placeholder = labelField.dataset?.placeholder;
|
||||
if ( ! placeholder ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update placeholder.
|
||||
const textarea = this.userMessageField.querySelector( 'textarea' );
|
||||
textarea.placeholder = placeholder;
|
||||
|
||||
// Append user message field.
|
||||
labelField.after( this.userMessageField );
|
||||
this.userMessageField.classList.remove( 'sui-hidden' );
|
||||
|
||||
// Focus on textarea.
|
||||
textarea.focus();
|
||||
}
|
||||
|
||||
getDeactivateLink() {
|
||||
return this.deactivatePluginLink.href;
|
||||
}
|
||||
|
||||
showModal() {
|
||||
const focusAfterClosed = 'wpbody-content',
|
||||
focusWhenOpen = undefined,
|
||||
hasOverlayMask = true,
|
||||
isCloseOnEsc = false,
|
||||
isAnimated = true;
|
||||
|
||||
window.SUI?.openModal(
|
||||
this.modalId,
|
||||
focusAfterClosed,
|
||||
focusWhenOpen,
|
||||
hasOverlayMask,
|
||||
isCloseOnEsc,
|
||||
isAnimated
|
||||
);
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
window.SUI?.closeModal( true );
|
||||
}
|
||||
|
||||
handleTrackDeactivate() {
|
||||
this.modal.addEventListener( 'afterClose', () => this.trackDeactivate(), { once: true } );
|
||||
}
|
||||
|
||||
trackDeactivate() {
|
||||
if ( ! this.shouldTrack() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const event = 'Deactivation Survey';
|
||||
const textarea = this.userMessageField.querySelector( 'textarea' );
|
||||
const message = textarea.value;
|
||||
const properties = {
|
||||
Reason: this.reason,
|
||||
Message: message,
|
||||
'Modal Action': this.modalAction,
|
||||
'Requested Assistance': this.requestedAssistance,
|
||||
'Tracking Status': tracker.allowToTrack() ? 'opted_in' : 'opted_out',
|
||||
};
|
||||
|
||||
Fetcher.common.request( {
|
||||
action: 'smush_track_deactivate',
|
||||
event,
|
||||
properties,
|
||||
} ).finally( () => {
|
||||
if ( this.shouldDeactivatePlugin() ) {
|
||||
this.redirectToDeactivateLink();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
shouldTrack() {
|
||||
return tracker.allowToTrack() || this.isSubmitAction();
|
||||
}
|
||||
|
||||
isSubmitAction() {
|
||||
return 'submit' === this.modalAction;
|
||||
}
|
||||
|
||||
shouldDeactivatePlugin() {
|
||||
const skipAndDeactivate = 'skip' === this.modalAction;
|
||||
|
||||
return skipAndDeactivate || this.isSubmitAction();
|
||||
}
|
||||
|
||||
redirectToDeactivateLink() {
|
||||
const deactivateLink = this.getDeactivateLink();
|
||||
window.location.href = deactivateLink;
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
|
||||
/**
|
||||
* Directory Smush module JavaScript code.
|
||||
*
|
||||
* @since 2.8.1 Separated from admin.js into dedicated file.
|
||||
*/
|
||||
|
||||
import { createTree } from 'jquery.fancytree';
|
||||
import Scanner from '../smush/directory-scanner';
|
||||
|
||||
( function( $ ) {
|
||||
'use strict';
|
||||
|
||||
WP_Smush.directory = {
|
||||
selected: [],
|
||||
tree: [],
|
||||
wp_smush_msgs: [],
|
||||
triggered: false,
|
||||
|
||||
init() {
|
||||
const self = this,
|
||||
progressDialog = $( '#wp-smush-progress-dialog' );
|
||||
|
||||
let totalSteps = 0,
|
||||
currentScanStep = 0;
|
||||
|
||||
// Make sure directory smush vars are set.
|
||||
if ( typeof window.wp_smushit_data.dir_smush !== 'undefined' ) {
|
||||
totalSteps = window.wp_smushit_data.dir_smush.totalSteps;
|
||||
currentScanStep =
|
||||
window.wp_smushit_data.dir_smush.currentScanStep;
|
||||
}
|
||||
|
||||
// Init image scanner.
|
||||
this.scanner = new Scanner( totalSteps, currentScanStep );
|
||||
|
||||
/**
|
||||
* Smush translation strings.
|
||||
*
|
||||
* @param {Array} wp_smush_msgs
|
||||
*/
|
||||
this.wp_smush_msgs = window.wp_smush_msgs || {};
|
||||
|
||||
/**
|
||||
* Open the "Select Smush directory" modal.
|
||||
*/
|
||||
$( 'button.wp-smush-browse, a#smush-directory-open-modal' ).on(
|
||||
'click',
|
||||
function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
if ( $( e.currentTarget ).hasClass( 'wp-smush-browse' ) ) {
|
||||
// Hide all the notices.
|
||||
$( 'div.wp-smush-scan-result div.wp-smush-notice' ).hide();
|
||||
|
||||
// Remove notice.
|
||||
$( 'div.wp-smush-info' ).remove();
|
||||
}
|
||||
|
||||
window.SUI.openModal(
|
||||
'wp-smush-list-dialog',
|
||||
e.currentTarget,
|
||||
$(
|
||||
'#wp-smush-list-dialog .sui-box-header [data-modal-close]'
|
||||
)[0],
|
||||
true
|
||||
);
|
||||
//Display File tree for Directory Smush
|
||||
self.initFileTree();
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Smush images: Smush in Choose Directory modal clicked
|
||||
*/
|
||||
$( '#wp-smush-select-dir' ).on( 'click', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
$( 'div.wp-smush-list-dialog div.sui-box-body' ).css( {
|
||||
opacity: '0.8',
|
||||
} );
|
||||
$( 'div.wp-smush-list-dialog div.sui-box-body a' ).off(
|
||||
'click'
|
||||
);
|
||||
|
||||
const button = $( this );
|
||||
|
||||
// Display the spinner.
|
||||
button.addClass('sui-button-onload');
|
||||
|
||||
const selectedFolders = self.tree.getSelectedNodes();
|
||||
|
||||
const paths = [];
|
||||
selectedFolders.forEach( function( folder ) {
|
||||
paths.push( folder.key );
|
||||
} );
|
||||
|
||||
// Send a ajax request to get a list of all the image files
|
||||
const param = {
|
||||
action: 'image_list',
|
||||
smush_path: paths,
|
||||
image_list_nonce: $(
|
||||
'input[name="image_list_nonce"]'
|
||||
).val(),
|
||||
};
|
||||
|
||||
$.post( ajaxurl, param, function( response ) {
|
||||
if ( response.success ) {
|
||||
// Close the modal.
|
||||
window.SUI.closeModal();
|
||||
|
||||
self.scanner = new Scanner( response.data, 0 );
|
||||
self.showProgressDialog( response.data );
|
||||
self.scanner.scan();
|
||||
} else {
|
||||
// Remove the spinner.
|
||||
button.removeClass('sui-button-onload');
|
||||
|
||||
window.SUI.openNotice(
|
||||
'wp-smush-ajax-notice',
|
||||
response.data.message,
|
||||
{ type: 'warning' }
|
||||
);
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Cancel scan.
|
||||
*/
|
||||
progressDialog.on(
|
||||
'click',
|
||||
'#cancel-directory-smush, #dialog-close-div, .wp-smush-cancel-dir',
|
||||
function( e ) {
|
||||
e.preventDefault();
|
||||
// Display the spinner
|
||||
$( '.wp-smush-cancel-dir' ).addClass( 'sui-button-onload' );
|
||||
self.scanner
|
||||
.cancel()
|
||||
.done(
|
||||
() => {
|
||||
const directorySmushUrl = `${ self.wp_smush_msgs.bulk_smush_url }&smush__directory-scan=done#directory_smush-settings-row`;
|
||||
if ( window.location.href === directorySmushUrl ) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
window.location.href = directorySmushUrl;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Continue scan.
|
||||
*/
|
||||
progressDialog.on(
|
||||
'click',
|
||||
'.sui-icon-play, .wp-smush-resume-scan',
|
||||
function( e ) {
|
||||
e.preventDefault();
|
||||
self.scanner.resume();
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Check to see if we should open the directory module.
|
||||
* Used to redirect from dashboard page.
|
||||
*
|
||||
* @since 3.8.6
|
||||
*/
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams( queryString );
|
||||
if ( urlParams.has( 'smush__directory-start' ) && ! this.triggered ) {
|
||||
this.triggered = true;
|
||||
$( 'button.wp-smush-browse' ).trigger( 'click' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Init fileTree.
|
||||
*/
|
||||
initFileTree() {
|
||||
const self = this,
|
||||
smushButton = $( 'button#wp-smush-select-dir' ),
|
||||
ajaxSettings = {
|
||||
type: 'GET',
|
||||
url: ajaxurl,
|
||||
data: {
|
||||
action: 'smush_get_directory_list',
|
||||
list_nonce: $( 'input[name="list_nonce"]' ).val(),
|
||||
},
|
||||
cache: false,
|
||||
};
|
||||
|
||||
// Object already defined.
|
||||
if ( Object.entries( self.tree ).length > 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.tree = createTree( '.wp-smush-list-dialog .content', {
|
||||
autoCollapse: true, // Automatically collapse all siblings, when a node is expanded
|
||||
clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand, 4:activate (dblclick expands)
|
||||
checkbox: true, // Show checkboxes
|
||||
debugLevel: 0, // 0:quiet, 1:errors, 2:warnings, 3:infos, 4:debug
|
||||
selectMode: 3, // 1:single, 2:multi, 3:multi-hier
|
||||
tabindex: '0', // Whole tree behaves as one single control
|
||||
keyboard: true, // Support keyboard navigation
|
||||
quicksearch: true, // Navigate to next node by typing the first letters
|
||||
source: ajaxSettings,
|
||||
lazyLoad: ( event, data ) => {
|
||||
data.result = new Promise( function( resolve, reject ) {
|
||||
ajaxSettings.data.dir = data.node.key;
|
||||
$.ajax( ajaxSettings )
|
||||
.done( ( response ) => resolve( response ) )
|
||||
.fail( reject );
|
||||
} );
|
||||
},
|
||||
loadChildren: ( event, data ) =>
|
||||
data.node.fixSelection3AfterClick(), // Apply parent's state to new child nodes:
|
||||
select: () =>
|
||||
smushButton.prop(
|
||||
'disabled',
|
||||
! +self.tree.getSelectedNodes().length
|
||||
),
|
||||
init: () => smushButton.prop( 'disabled', true ),
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Show progress dialog.
|
||||
*
|
||||
* @param {number} items Number of items in the scan.
|
||||
*/
|
||||
showProgressDialog( items ) {
|
||||
// Update items status and show the progress dialog..
|
||||
$( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
|
||||
'0/' + items + ' ' + self.wp_smush_msgs.progress_smushed
|
||||
);
|
||||
|
||||
window.SUI.openModal(
|
||||
'wp-smush-progress-dialog',
|
||||
'dialog-close-div',
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update progress bar during directory smush.
|
||||
*
|
||||
* @param {number} progress Current progress in percent.
|
||||
* @param {boolean} cancel Cancel status.
|
||||
*/
|
||||
updateProgressBar( progress, cancel = false ) {
|
||||
if ( progress > 100 ) {
|
||||
progress = 100;
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
$( '.sui-progress-block .sui-progress-text span' ).text(
|
||||
progress + '%'
|
||||
);
|
||||
$( '.sui-progress-block .sui-progress-bar span' ).width(
|
||||
progress + '%'
|
||||
);
|
||||
|
||||
if ( progress >= 90 ) {
|
||||
$( '.sui-progress-state .sui-progress-state-text' ).text(
|
||||
'Finalizing...'
|
||||
);
|
||||
}
|
||||
|
||||
if ( cancel ) {
|
||||
$( '.sui-progress-state .sui-progress-state-text' ).text(
|
||||
'Cancelling...'
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
WP_Smush.directory.init();
|
||||
}( jQuery ) );
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
// Disconnect Site.
|
||||
/* global WP_Smush */
|
||||
|
||||
import Fetcher from '../utils/fetcher';
|
||||
import tracker from '../utils/tracker';
|
||||
|
||||
class DisconnectSite {
|
||||
constructor() {
|
||||
document.addEventListener( 'DOMContentLoaded', () => {
|
||||
this.modalAction = 'cancel';
|
||||
this.modalId = 'smush-disconnect-site-modal';
|
||||
this.modal = document.getElementById( this.modalId );
|
||||
|
||||
this._onAfterClose = this.trackDisconnectSite.bind( this );
|
||||
if ( this.modal ) {
|
||||
this.modal.addEventListener( 'afterClose', this._onAfterClose, { once: true } );
|
||||
document.addEventListener( 'onSavedSmushSettings', ( e ) => {
|
||||
const usage = document.getElementById( 'usage' );
|
||||
if ( usage ) {
|
||||
tracker.setAllowToTrack( usage.checked );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
trackDisconnectSite() {
|
||||
const textarea = document.getElementById( 'smush-disconnect-site-message' );
|
||||
const message = textarea.value.trim();
|
||||
const skipMessage = message.length === 0;
|
||||
|
||||
if ( skipMessage && this.isSubmitAction() ) {
|
||||
this.setModalAction( 'skip' );
|
||||
}
|
||||
|
||||
if ( ! this.shouldTrack() ) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const event = 'Disconnect Site';
|
||||
const properties = {
|
||||
'User Message': message,
|
||||
'Modal Action': this.modalAction,
|
||||
'Tracking Status': tracker.allowToTrack() ? 'opted_in' : 'opted_out',
|
||||
};
|
||||
|
||||
return tracker.setAllowToTrack( true ).track( event, properties );
|
||||
}
|
||||
|
||||
shouldTrack() {
|
||||
return tracker.allowToTrack() || this.isSubmitAction();
|
||||
}
|
||||
|
||||
isSubmitAction() {
|
||||
return 'submit' === this.modalAction;
|
||||
}
|
||||
|
||||
setModalAction( action ) {
|
||||
this.modalAction = action;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
if ( ! this.modal ) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise( ( resolve ) => {
|
||||
// Ensure only one event listener is active.
|
||||
this.modal.removeEventListener( 'afterClose', this._onAfterClose );
|
||||
this.modal.addEventListener( 'afterClose', async () => {
|
||||
await this.trackDisconnectSite();
|
||||
resolve();
|
||||
}, { once: true } );
|
||||
|
||||
window.SUI?.closeModal( true );
|
||||
} );
|
||||
}
|
||||
|
||||
disconnect( btn ) {
|
||||
if ( btn ) {
|
||||
btn.classList.add( 'sui-button-onload-text' );
|
||||
}
|
||||
|
||||
return Fetcher.settings.disconnectSite().then( async ( res ) => {
|
||||
if ( res.success ) {
|
||||
await this.setModalAction( 'submit' ).closeModal();
|
||||
window.location.search = window.location.search + `&smush-notice=site-disconnected`;
|
||||
} else {
|
||||
WP_Smush.helpers.showNotice( res );
|
||||
}
|
||||
} ).catch( ( error ) => {
|
||||
WP_Smush.helpers.showNotice( error );
|
||||
} ).finally( () => {
|
||||
if ( btn ) {
|
||||
btn.classList.remove( 'sui-button-onload-text' );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
const disconnectSite = new DisconnectSite();
|
||||
|
||||
export default disconnectSite;
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
/* global wp_smush_msgs */
|
||||
|
||||
/**
|
||||
* Helpers functions.
|
||||
*
|
||||
* @since 2.9.0 Moved from admin.js
|
||||
*/
|
||||
( function() {
|
||||
'use strict';
|
||||
|
||||
WP_Smush.helpers = {
|
||||
init: () => {},
|
||||
cacheUpsellErrorCodes: [],
|
||||
|
||||
/**
|
||||
* Convert bytes to human-readable form.
|
||||
*
|
||||
* @param {number} a Bytes
|
||||
* @param {number} b Number of digits
|
||||
* @return {*} Formatted Bytes
|
||||
*/
|
||||
formatBytes: ( a, b ) => {
|
||||
const thresh = 1024,
|
||||
units = [ 'KB', 'MB', 'GB', 'TB', 'PB' ];
|
||||
|
||||
if ( Math.abs( a ) < thresh ) {
|
||||
return a + ' B';
|
||||
}
|
||||
|
||||
let u = -1;
|
||||
|
||||
do {
|
||||
a /= thresh;
|
||||
++u;
|
||||
} while ( Math.abs( a ) >= thresh && u < units.length - 1 );
|
||||
|
||||
return a.toFixed( b ) + ' ' + units[ u ];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get size from a string.
|
||||
*
|
||||
* @param {string} formattedSize Formatter string
|
||||
* @return {*} Formatted Bytes
|
||||
*/
|
||||
getSizeFromString: ( formattedSize ) => {
|
||||
return formattedSize.replace( /[a-zA-Z]/g, '' ).trim();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get type from formatted string.
|
||||
*
|
||||
* @param {string} formattedSize Formatted string
|
||||
* @return {*} Formatted Bytes
|
||||
*/
|
||||
getFormatFromString: ( formattedSize ) => {
|
||||
return formattedSize.replace( /[0-9.]/g, '' ).trim();
|
||||
},
|
||||
|
||||
/**
|
||||
* Stackoverflow: http://stackoverflow.com/questions/1726630/formatting-a-number-with-exactly-two-decimals-in-javascript
|
||||
*
|
||||
* @param {number} num
|
||||
* @param {number} decimals
|
||||
* @return {number} Number
|
||||
*/
|
||||
precise_round: ( num, decimals ) => {
|
||||
const sign = num >= 0 ? 1 : -1;
|
||||
// Keep the percentage below 100.
|
||||
num = num > 100 ? 100 : num;
|
||||
return (
|
||||
Math.round( num * Math.pow( 10, decimals ) + sign * 0.001 ) /
|
||||
Math.pow( 10, decimals )
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays a floating error message using the #wp-smush-ajax-notice container.
|
||||
*
|
||||
* @since 3.8.0
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
showErrorNotice: ( message ) => {
|
||||
if ( 'undefined' === typeof message ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noticeMessage = `<p>${ message }</p>`,
|
||||
noticeOptions = {
|
||||
type: 'error',
|
||||
icon: 'info',
|
||||
};
|
||||
|
||||
SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
|
||||
|
||||
const loadingButton = document.querySelector( '.sui-button-onload' );
|
||||
if ( loadingButton ) {
|
||||
loadingButton.classList.remove( 'sui-button-onload' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset settings.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
resetSettings: () => {
|
||||
const _nonce = document.getElementById( 'wp_smush_reset' );
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=reset_settings', true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if ( 200 === xhr.status ) {
|
||||
const res = JSON.parse( xhr.response );
|
||||
if ( 'undefined' !== typeof res.success && res.success ) {
|
||||
window.location.href = wp_smush_msgs.smush_url;
|
||||
}
|
||||
} else {
|
||||
window.console.log(
|
||||
'Request failed. Returned status of ' + xhr.status
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send( '_ajax_nonce=' + _nonce.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare error row. Will only allow to hide errors for WP media attachments (not nextgen).
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @since 3.12.0 Moved from Smush.
|
||||
*
|
||||
* @param {string} errorMsg Error message.
|
||||
* @param {string} fileName File name.
|
||||
* @param {string} thumbnail Thumbnail for image (if available).
|
||||
* @param {number} id Image ID.
|
||||
* @param {string} type Smush type: media or netxgen.
|
||||
* @param {string} errorCode Error code.
|
||||
*
|
||||
* @return {string} Row with error.
|
||||
*/
|
||||
prepareBulkSmushErrorRow: (errorMsg, fileName, thumbnail, id, type, errorCode) => {
|
||||
const thumbDiv =
|
||||
thumbnail && 'undefined' !== typeof thumbnail ?
|
||||
`<img class="attachment-thumbnail" src="${thumbnail}" />` :
|
||||
'<i class="sui-icon-photo-picture" aria-hidden="true"></i>';
|
||||
const editLink = window.wp_smush_msgs.edit_link.replace('{{id}}', id);
|
||||
fileName =
|
||||
'undefined' === fileName || 'undefined' === typeof fileName ?
|
||||
'undefined' :
|
||||
fileName;
|
||||
|
||||
let tableDiv =
|
||||
`<div class="smush-bulk-error-row" data-error-code="${errorCode}">
|
||||
<div class="smush-bulk-image-data">
|
||||
<div class="smush-bulk-image-title">
|
||||
${ thumbDiv }
|
||||
<span class="smush-image-name">
|
||||
<a href="${editLink}">${fileName}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="smush-image-error">
|
||||
${errorMsg}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if ('media' === type) {
|
||||
tableDiv +=
|
||||
`<div class="smush-bulk-image-actions">
|
||||
<a href="javascript:void(0)" class="sui-tooltip sui-tooltip-constrained sui-tooltip-left smush-ignore-image" data-tooltip="${window.wp_smush_msgs.error_ignore}" data-id="${id}">
|
||||
${window.wp_smush_msgs.btn_ignore}
|
||||
</a>
|
||||
<a class="smush-link-detail" href="${editLink}">
|
||||
${window.wp_smush_msgs.view_detail}
|
||||
</a>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
tableDiv += '</div>';
|
||||
|
||||
tableDiv += WP_Smush.helpers.upsellWithError( errorCode );
|
||||
|
||||
return tableDiv;
|
||||
},
|
||||
cacheUpsellErrorCode( errorCode ) {
|
||||
this.cacheUpsellErrorCodes.push( errorCode );
|
||||
},
|
||||
/**
|
||||
* Get upsell base on error code.
|
||||
* @param {string} errorCode Error code.
|
||||
* @returns {string}
|
||||
*/
|
||||
upsellWithError(errorCode) {
|
||||
if (
|
||||
!errorCode
|
||||
|| !window.wp_smush_msgs['error_' + errorCode]
|
||||
|| this.isUpsellRendered( errorCode )
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
this.cacheRenderedUpsell( errorCode );
|
||||
|
||||
return '<div class="smush-bulk-error-row smush-error-upsell">' +
|
||||
'<div class="smush-bulk-image-title">' +
|
||||
'<span class="smush-image-error">' +
|
||||
window.wp_smush_msgs['error_' + errorCode] +
|
||||
'</span>' +
|
||||
'</div></div>';
|
||||
},
|
||||
// Do not use arrow function to use `this`.
|
||||
isUpsellRendered( errorCode ) {
|
||||
return this.cacheUpsellErrorCodes.includes( errorCode );
|
||||
},
|
||||
// Do not use arrow function to use `this`.
|
||||
cacheRenderedUpsell( errorCode ) {
|
||||
this.cacheUpsellErrorCodes.push( errorCode );
|
||||
},
|
||||
/**
|
||||
* Get error message from Ajax response or Error.
|
||||
* @param {Object} resp
|
||||
*/
|
||||
getErrorMessage: ( resp ) => {
|
||||
return resp.message || resp.data && resp.data.message ||
|
||||
resp.responseJSON && resp.responseJSON.data && resp.responseJSON.data.message ||
|
||||
window.wp_smush_msgs.generic_ajax_error ||
|
||||
resp.status && 'Request failed. Returned status of ' + resp.status
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays a floating message from response,
|
||||
* using the #wp-smush-ajax-notice container.
|
||||
*
|
||||
* @param {Object|string} notice
|
||||
* @param {Object} noticeOptions
|
||||
*/
|
||||
showNotice: function( notice, noticeOptions ) {
|
||||
let message;
|
||||
if ( 'object' === typeof notice ) {
|
||||
message = this.getErrorMessage( notice );
|
||||
} else {
|
||||
message = notice;
|
||||
}
|
||||
|
||||
if ( ! message ) {
|
||||
return;
|
||||
}
|
||||
|
||||
noticeOptions = noticeOptions || {};
|
||||
noticeOptions = Object.assign({
|
||||
showdismiss: false,
|
||||
autoclose: true,
|
||||
},noticeOptions);
|
||||
noticeOptions = {
|
||||
type: noticeOptions.type || 'error',
|
||||
icon: noticeOptions.icon || ( 'success' === noticeOptions.type ? 'check-tick' : 'info' ),
|
||||
dismiss: {
|
||||
show: noticeOptions.showdismiss,
|
||||
label: window.wp_smush_msgs.noticeDismiss,
|
||||
tooltip: window.wp_smush_msgs.noticeDismissTooltip,
|
||||
},
|
||||
autoclose: {
|
||||
show: noticeOptions.autoclose
|
||||
}
|
||||
};
|
||||
|
||||
const noticeMessage = `<p>${ message }</p>`;
|
||||
|
||||
SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
|
||||
return Promise.resolve( '#wp-smush-ajax-notice' );
|
||||
},
|
||||
closeNotice() {
|
||||
window.SUI.closeNotice( 'wp-smush-ajax-notice' );
|
||||
},
|
||||
renderActivationCDNNotice: function( noticeMessage ) {
|
||||
const animatedNotice = document.getElementById('wp-smush-animated-upsell-notice');
|
||||
if ( animatedNotice ) {
|
||||
return;
|
||||
}
|
||||
const upsellHtml = `<div class="sui-notice sui-notice-info sui-margin-top" id="wp-smush-animated-upsell-notice">
|
||||
<div class="sui-notice-content">
|
||||
<div class="sui-notice-message">
|
||||
<i class="sui-notice-icon sui-icon-info" aria-hidden="true"></i>
|
||||
<p>${noticeMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
document.querySelector( '#smush-box-bulk .wp-smush-bulk-wrapper' ).outerHTML += upsellHtml;
|
||||
},
|
||||
redirectToPage( page ) {
|
||||
page = `page=smush-${page}`;
|
||||
if ( window.location.href.includes( page ) ) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
window.location.search = page;
|
||||
}
|
||||
},
|
||||
showModal( modalId, options = {} ) {
|
||||
if ( ! window.SUI ) {
|
||||
return;
|
||||
}
|
||||
|
||||
options = Object.assign( {
|
||||
focusAfterClosed: 'wpbody-content',
|
||||
focusWhenOpen: undefined,
|
||||
hasOverlayMask: false,
|
||||
isCloseOnEsc: false,
|
||||
isAnimated: true,
|
||||
}, options );
|
||||
|
||||
window.SUI.openModal(
|
||||
modalId,
|
||||
options.focusAfterClosed,
|
||||
options.focusWhenOpen,
|
||||
options.hasOverlayMask,
|
||||
options.isCloseOnEsc,
|
||||
options.isAnimated
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
WP_Smush.helpers.init();
|
||||
}() );
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
/* global WP_Smush */
|
||||
|
||||
/**
|
||||
* Scan Media Library.
|
||||
*
|
||||
*/
|
||||
import SmushProgress from '../common/progressbar';
|
||||
import MediaLibraryScanner from '../common/media-library-scanner';
|
||||
import { GlobalStats } from '../common/globalStats';
|
||||
|
||||
( function() {
|
||||
'use strict';
|
||||
if ( ! window.wp_smush_msgs ) {
|
||||
return;
|
||||
}
|
||||
const $ = document.querySelector.bind( document );
|
||||
const existScanProgressBar = $( '.wp-smush-scan-progress-bar-wrapper' );
|
||||
if ( ! existScanProgressBar ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recheckImagesBtn = $( '.wp-smush-scan' );
|
||||
if ( ! recheckImagesBtn ) {
|
||||
return;
|
||||
}
|
||||
const bulkSmushButton = $( '.wp-smush-bo-start' ) || $( '.wp-smush-bulk-wrapper .wp-smush-all' );
|
||||
const { __ } = wp.i18n;
|
||||
|
||||
class MediaLibraryScannerOnBulkSmush extends MediaLibraryScanner {
|
||||
constructor() {
|
||||
super();
|
||||
this.runBulkSmushOnComplete = false;
|
||||
this.restoreButton = $( '.wp-smush-restore' );
|
||||
this.autoBulkSmushNotification = $( '.wp-smush-auto-bulk-smush-notification' );
|
||||
}
|
||||
|
||||
startScanThenBulkSmushOnComplete() {
|
||||
this.runBulkSmushOnComplete = true;
|
||||
this.startScan( true );
|
||||
}
|
||||
|
||||
onStart() {
|
||||
this.hideRecheckNotice();
|
||||
this.hideFailedBulkSmushNotice();
|
||||
this.disableRelatedButtons();
|
||||
this.setRecheckImagesButtonOnLoad();
|
||||
this.toggleBulkSmushBoxContent();
|
||||
return this;
|
||||
}
|
||||
|
||||
onStartFailure( response ) {
|
||||
super.onStartFailure( response );
|
||||
this.revertRelatedButtons();
|
||||
}
|
||||
|
||||
onCloseProgressBar() {
|
||||
this.maybeHideAutoBulkSmushNotification();
|
||||
}
|
||||
|
||||
disableRelatedButtons() {
|
||||
this.restoreButton.setAttribute( 'disabled', true );
|
||||
if ( bulkSmushButton ) {
|
||||
bulkSmushButton.setAttribute( 'disabled', true );
|
||||
this.setInnerText( bulkSmushButton, __( 'Waiting for Re-check to finish', 'wp-smushit' ) );
|
||||
}
|
||||
}
|
||||
|
||||
revertRelatedButtons() {
|
||||
if ( bulkSmushButton ) {
|
||||
bulkSmushButton.removeAttribute( 'disabled' );
|
||||
this.revertInnerText( bulkSmushButton );
|
||||
}
|
||||
this.restoreButton.removeAttribute( 'disabled' );
|
||||
this.revertRecheckImagesButton();
|
||||
return this;
|
||||
}
|
||||
|
||||
setRecheckImagesButtonOnLoad() {
|
||||
// recheckImagesBtn.classList.add( 'sui-button-onload' );
|
||||
this.disableRecheckImagesButton();
|
||||
this.setInnerText( recheckImagesBtn.querySelector( '.wp-smush-inner-text' ), __( 'Checking Images', 'wp-smushit' ) );
|
||||
}
|
||||
|
||||
disableRecheckImagesButton() {
|
||||
recheckImagesBtn.setAttribute( 'disabled', true );
|
||||
}
|
||||
|
||||
revertRecheckImagesButton() {
|
||||
// recheckImagesBtn.classList.remove( 'sui-button-onload' );
|
||||
recheckImagesBtn.removeAttribute( 'disabled' );
|
||||
this.revertInnerText( recheckImagesBtn.querySelector( '.wp-smush-inner-text' ) );
|
||||
}
|
||||
|
||||
beforeUpdateStatus( stats ) {
|
||||
this.runBulkSmushOnComplete = stats?.optimize_on_scan_completed;
|
||||
this.maybeShowAutoBulkSmushNotification();
|
||||
}
|
||||
|
||||
onDead( stats ) {
|
||||
super.onDead( stats );
|
||||
this.revertRelatedButtons();
|
||||
this.setRequiredScanForBulkSmushButton();
|
||||
}
|
||||
|
||||
onFinish( stats ) {
|
||||
const globalStats = stats.global_stats;
|
||||
super.onFinish( stats );
|
||||
this.revertRelatedButtons();
|
||||
this.toggleBulkSmushDescription( globalStats );
|
||||
if ( globalStats.is_outdated ) {
|
||||
this.setRequiredScanForBulkSmushButton();
|
||||
} else {
|
||||
this.removeScanEventFromBulkSmushButton();
|
||||
}
|
||||
|
||||
this.revertRecheckWarning();
|
||||
}
|
||||
|
||||
onCompleted( stats ) {
|
||||
const requiredReloadPage = ! bulkSmushButton;
|
||||
if ( requiredReloadPage ) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
this.onFinish( stats );
|
||||
const globalStats = stats.global_stats;
|
||||
const allImagesSmushed = globalStats.remaining_count < 1;
|
||||
if ( allImagesSmushed ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! this.runBulkSmushOnComplete ) {
|
||||
this.showRecheckNoticeSuccess();
|
||||
return;
|
||||
}
|
||||
this.runBulkSmushOnComplete = false;
|
||||
|
||||
this.triggerBulkSmushEvent( stats );
|
||||
}
|
||||
|
||||
showNotice( stats ) {
|
||||
if ( ! stats.notice ) {
|
||||
return;
|
||||
}
|
||||
let type = 'success';
|
||||
if ( 'undefined' !== typeof stats.noticeType ) {
|
||||
type = stats.noticeType;
|
||||
}
|
||||
window.SUI.openNotice(
|
||||
'wp-smush-ajax-notice',
|
||||
'<p>' + stats.notice + '</p>',
|
||||
{ type, icon: 'check-tick' }
|
||||
);
|
||||
}
|
||||
|
||||
showRecheckNoticeSuccess() {
|
||||
const recheckNotice = $( '.wp-smush-recheck-images-notice-box' );
|
||||
if ( ! recheckNotice ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hideFailedBulkSmushNotice();
|
||||
this.showAnElement( recheckNotice );
|
||||
this.hideAnElement( recheckNotice.querySelector( '.wp-smush-recheck-images-notice-warning' ) );
|
||||
this.showAnElement( recheckNotice.querySelector( '.wp-smush-recheck-images-notice-success' ) );
|
||||
}
|
||||
|
||||
showRecheckNoticeWarning() {
|
||||
const recheckNotice = $( '.wp-smush-recheck-images-notice-box' );
|
||||
if ( ! recheckNotice ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hideFailedBulkSmushNotice();
|
||||
this.showAnElement( recheckNotice );
|
||||
this.hideAnElement( recheckNotice.querySelector( '.wp-smush-recheck-images-notice-success' ) );
|
||||
this.showAnElement( recheckNotice.querySelector( '.wp-smush-recheck-images-notice-warning' ) );
|
||||
}
|
||||
|
||||
hideRecheckNotice() {
|
||||
this.hideAnElement( $( '.wp-smush-recheck-images-notice-box' ) );
|
||||
}
|
||||
|
||||
hideFailedBulkSmushNotice() {
|
||||
this.hideAnElement( $( '#smush-box-inline-retry-bulk-smush-notice' ) );
|
||||
}
|
||||
|
||||
showProgressErrorNoticeOnRecheckNotice() {
|
||||
const recheckWarningElement = $( '.wp-smush-recheck-images-notice-box .wp-smush-recheck-images-notice-warning' );
|
||||
if ( ! recheckWarningElement ) {
|
||||
return;
|
||||
}
|
||||
recheckWarningElement.classList.add( 'sui-notice-error' );
|
||||
recheckWarningElement.classList.remove( 'sui-notice-warning' );
|
||||
this.showRecheckNoticeWarning();
|
||||
}
|
||||
|
||||
revertRecheckWarning() {
|
||||
const recheckWarningElement = $( '.wp-smush-recheck-images-notice-box .wp-smush-recheck-images-notice-warning' );
|
||||
if ( ! recheckWarningElement ) {
|
||||
return;
|
||||
}
|
||||
recheckWarningElement.classList.add( 'sui-notice-warning' );
|
||||
recheckWarningElement.classList.remove( 'sui-notice-error' );
|
||||
this.revertInnerText( recheckWarningElement.querySelector( 'span' ) );
|
||||
}
|
||||
|
||||
triggerBulkSmushEvent( stats ) {
|
||||
this.disableRecheckImagesButton();
|
||||
|
||||
if ( stats.enabled_background_process ) {
|
||||
this.triggerBackgroundBulkSmushEvent( stats.global_stats );
|
||||
} else {
|
||||
this.triggerAjaxBulkSmushEvent( stats.global_stats );
|
||||
}
|
||||
}
|
||||
|
||||
toggleBulkSmushDescription( globalStats ) {
|
||||
if ( SmushProgress.isEmptyObject ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( globalStats.remaining_count < 1 ) {
|
||||
SmushProgress.hideBulkSmushDescription();
|
||||
SmushProgress.showBulkSmushAllDone();
|
||||
} else {
|
||||
SmushProgress.showBulkSmushDescription();
|
||||
SmushProgress.hideBulkSmushAllDone();
|
||||
}
|
||||
}
|
||||
|
||||
setRequiredScanForBulkSmushButton() {
|
||||
bulkSmushButton && bulkSmushButton.classList.add( 'wp-smush-scan-and-bulk-smush' );
|
||||
}
|
||||
|
||||
removeScanEventFromBulkSmushButton() {
|
||||
bulkSmushButton && bulkSmushButton.classList.remove( 'wp-smush-scan-and-bulk-smush' );
|
||||
}
|
||||
|
||||
triggerBackgroundBulkSmushEvent( globalStats ) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent( 'backgroundBulkSmushOnScanCompleted', {
|
||||
detail: globalStats
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
triggerAjaxBulkSmushEvent( globalStats ) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent( 'ajaxBulkSmushOnScanCompleted', {
|
||||
detail: globalStats
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
onCancelled( stats ) {
|
||||
this.onFinish( stats );
|
||||
this.runBulkSmushOnComplete = false;
|
||||
this.setRequiredScanForBulkSmushButton();
|
||||
}
|
||||
|
||||
maybeShowAutoBulkSmushNotification() {
|
||||
if (
|
||||
! this.runBulkSmushOnComplete
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.showAnElement( this.autoBulkSmushNotification );
|
||||
}
|
||||
|
||||
maybeHideAutoBulkSmushNotification() {
|
||||
if (
|
||||
! this.runBulkSmushOnComplete
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.hideAnElement( this.autoBulkSmushNotification );
|
||||
}
|
||||
|
||||
toggleBulkSmushBoxContent() {
|
||||
GlobalStats.resetAndHideBulkErrors();
|
||||
this.toggleBulkSmushDescription( GlobalStats.getGlobalStats() );
|
||||
}
|
||||
}
|
||||
const mediaLibScanner = new MediaLibraryScannerOnBulkSmush();
|
||||
|
||||
/**
|
||||
* Event Listeners.
|
||||
*/
|
||||
|
||||
// Background Scan Media Library.
|
||||
const registerScanMediaLibraryEvent = () => {
|
||||
if ( ! recheckImagesBtn ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const canScanInBackground = recheckImagesBtn.classList.contains( 'wp-smush-background-scan' );
|
||||
if ( ! canScanInBackground ) {
|
||||
return;
|
||||
}
|
||||
|
||||
recheckImagesBtn.addEventListener( 'click', () => mediaLibScanner.startScan() );
|
||||
|
||||
//Check scan is running.
|
||||
if ( window.wp_smushit_data.media_library_scan?.in_processing ) {
|
||||
mediaLibScanner.onStart().showProgressBar().autoSyncStatus();
|
||||
}
|
||||
};
|
||||
|
||||
const maybeAutoStartMediaLibraryScanOnRedirect = () => {
|
||||
if ( ! recheckImagesBtn || ! window.location.search.includes( 'smush-action=start-scan-media' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
recheckImagesBtn.click();
|
||||
const removeScanActionFromURLAddress = () => {
|
||||
const cleanedURL = window.location.href.replace( '&smush-action=start-scan-media', '' );
|
||||
window.history.pushState( null, null, cleanedURL );
|
||||
};
|
||||
removeScanActionFromURLAddress();
|
||||
};
|
||||
|
||||
/**
|
||||
* Recheck Images Notice events.
|
||||
*/
|
||||
const registerEventsRelatedInlineNoticeOnBulkSmushPage = () => {
|
||||
const recheckImagesNotice = $( '.wp-smush-recheck-images-notice-box' );
|
||||
if ( ! recheckImagesNotice || ! recheckImagesBtn ) {
|
||||
return;
|
||||
}
|
||||
const triggerBackgroundScanImagesLink = recheckImagesNotice.querySelector( '.wp-smush-trigger-background-scan' );
|
||||
if ( triggerBackgroundScanImagesLink ) {
|
||||
triggerBackgroundScanImagesLink.onclick = ( e ) => {
|
||||
e.preventDefault();
|
||||
recheckImagesBtn.click();
|
||||
};
|
||||
|
||||
if ( ! window.location.search.includes( 'smush-action=start-scan-media' ) ) {
|
||||
if ( window.wp_smushit_data.media_library_scan?.is_dead ) {
|
||||
mediaLibScanner.showProgressErrorNoticeOnRecheckNotice();
|
||||
} else if( window.wp_smushit_data.is_outdated && ! window.wp_smushit_data?.bo_stats?.is_dead ) {
|
||||
mediaLibScanner.showRecheckNoticeWarning();
|
||||
}
|
||||
}
|
||||
}
|
||||
const triggerBulkSmush = recheckImagesNotice.querySelector( '.wp-smush-trigger-bulk-smush' );
|
||||
if ( triggerBulkSmush && bulkSmushButton ) {
|
||||
triggerBulkSmush.onclick = ( e ) => {
|
||||
e.preventDefault();
|
||||
recheckImagesNotice.classList.add( 'sui-hidden' );
|
||||
bulkSmushButton.click();
|
||||
};
|
||||
}
|
||||
const dismissNotices = recheckImagesNotice.querySelectorAll( 'button.sui-button-icon' );
|
||||
if ( dismissNotices ) {
|
||||
dismissNotices.forEach( ( dismissNotice ) => {
|
||||
dismissNotice.onclick = ( e ) => {
|
||||
dismissNotice.closest( '.sui-recheck-images-notice' ).classList.add( 'sui-hidden' );
|
||||
};
|
||||
} );
|
||||
}
|
||||
|
||||
document.addEventListener( 'onSavedSmushSettings', function( e ) {
|
||||
if ( ! e?.detail?.is_outdated_stats ) {
|
||||
return;
|
||||
}
|
||||
|
||||
mediaLibScanner.setRequiredScanForBulkSmushButton();
|
||||
|
||||
const bulkSmushFailedNotice = document.querySelector( '#smush-box-inline-retry-bulk-smush-notice' );
|
||||
const isShowingBulkSmushFailedNotice = bulkSmushFailedNotice && ! bulkSmushFailedNotice.classList.contains( 'sui-hidden' );
|
||||
if ( isShowingBulkSmushFailedNotice ) {
|
||||
return;
|
||||
}
|
||||
|
||||
recheckImagesNotice.classList.remove( 'sui-hidden' );
|
||||
recheckImagesNotice.querySelector( '.wp-smush-recheck-images-notice-success' ).classList.add( 'sui-hidden' );
|
||||
recheckImagesNotice.querySelector( '.wp-smush-recheck-images-notice-warning' ).classList.remove( 'sui-hidden' );
|
||||
} );
|
||||
};
|
||||
|
||||
// Scan and Bulk Smush.
|
||||
const registerScanAndBulkSmushEvent = () => {
|
||||
if ( ! bulkSmushButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleScanAndBulkSmush = ( e ) => {
|
||||
const shouldRunScan = bulkSmushButton.classList.contains( 'wp-smush-scan-and-bulk-smush' );
|
||||
if ( ! shouldRunScan ) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
mediaLibScanner.startScanThenBulkSmushOnComplete();
|
||||
};
|
||||
|
||||
bulkSmushButton.addEventListener( 'click', handleScanAndBulkSmush );
|
||||
};
|
||||
|
||||
const autoStartBulkSmushOnRedirect = () => {
|
||||
if ( ! bulkSmushButton || ! window.location.search.includes( 'smush-action=start-bulk-' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
bulkSmushButton.click();
|
||||
|
||||
const removeSmushActionFromURLAddress = () => {
|
||||
const cleanedURL = window.location.href.replace( /&smush-action=start-bulk-(smush|webp-conversion)/i, '' );
|
||||
window.history.pushState( null, null, cleanedURL );
|
||||
};
|
||||
removeSmushActionFromURLAddress();
|
||||
};
|
||||
|
||||
const registerRetryProcessFromLoopbackErrorModal = () => {
|
||||
const loopbackErrorModal = document.getElementById( 'smush-loopback-error-dialog' );
|
||||
if ( ! loopbackErrorModal ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const retryButton = loopbackErrorModal.querySelector( '.smush-retry-process-button' );
|
||||
if ( retryButton ) {
|
||||
retryButton.addEventListener( 'click', () => {
|
||||
const processType = loopbackErrorModal.dataset?.processType || 'scan';
|
||||
if ( 'scan' === processType ) {
|
||||
recheckImagesBtn.click();
|
||||
} else {
|
||||
bulkSmushButton.click();
|
||||
}
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
registerScanMediaLibraryEvent();
|
||||
registerScanAndBulkSmushEvent();
|
||||
registerEventsRelatedInlineNoticeOnBulkSmushPage();
|
||||
maybeAutoStartMediaLibraryScanOnRedirect();
|
||||
autoStartBulkSmushOnRedirect();
|
||||
registerRetryProcessFromLoopbackErrorModal();
|
||||
}() );
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/* global WP_Smush */
|
||||
|
||||
/**
|
||||
* Scan Media Library.
|
||||
*
|
||||
*/
|
||||
import MediaLibraryScanner from '../common/media-library-scanner';
|
||||
|
||||
( function() {
|
||||
'use strict';
|
||||
if ( ! window.wp_smush_msgs ) {
|
||||
return;
|
||||
}
|
||||
const $ = document.querySelector.bind( document );
|
||||
const existScanProgressBar = $( '.wp-smush-scan-progress-bar-wrapper' );
|
||||
if ( ! existScanProgressBar ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recheckImagesBtn = $( '.wp-smush-scan' );
|
||||
if ( recheckImagesBtn ) {
|
||||
return;
|
||||
}
|
||||
//Check scan is running.
|
||||
const is_scan_running = window.wp_smushit_data.media_library_scan?.in_processing;
|
||||
if ( ! is_scan_running ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { __ } = wp.i18n;
|
||||
|
||||
class mediaLibraryScannerOnDashboard extends MediaLibraryScanner {
|
||||
constructor() {
|
||||
super();
|
||||
this.bulkSmushLink = $( '.wp-smush-bulk-smush-link' );
|
||||
}
|
||||
onShowProgressBar() {
|
||||
this.disableBulkSmushLink();
|
||||
}
|
||||
|
||||
onCloseProgressBar() {
|
||||
this.revertBulkSmushLink();
|
||||
}
|
||||
|
||||
disableBulkSmushLink() {
|
||||
if ( ! this.bulkSmushLink ) {
|
||||
return;
|
||||
}
|
||||
this.bulkSmushLink.setAttribute( 'disabled', true );
|
||||
this.setInnerText( this.bulkSmushLink, __( 'Waiting for Re-check to finish', 'wp-smushit' ) );
|
||||
}
|
||||
|
||||
revertBulkSmushLink() {
|
||||
if ( ! this.bulkSmushLink ) {
|
||||
return;
|
||||
}
|
||||
this.bulkSmushLink.removeAttribute( 'disabled' );
|
||||
this.revertInnerText( this.bulkSmushLink );
|
||||
}
|
||||
}
|
||||
|
||||
( new mediaLibraryScannerOnDashboard() ).showProgressBar().autoSyncStatus();
|
||||
}() );
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import Smush from '../smush/smush';
|
||||
import SmushProcess from '../common/progressbar';
|
||||
|
||||
(function($) {
|
||||
$(function() {
|
||||
/** Handle NextGen Gallery smush button click **/
|
||||
$('body').on('click', '.wp-smush-nextgen-send', function (e) {
|
||||
// prevent the default action
|
||||
e.preventDefault();
|
||||
new Smush($(this), false, 'nextgen');
|
||||
});
|
||||
|
||||
/** Handle NextGen Gallery Bulk smush button click **/
|
||||
$('body').on('click', '.wp-smush-nextgen-bulk', function (e) {
|
||||
// prevent the default action
|
||||
e.preventDefault();
|
||||
|
||||
// Remove existing Re-Smush notices.
|
||||
// TODO: REMOVE re-smush-notice since no longer used.
|
||||
$('.wp-smush-resmush-notice').remove();
|
||||
|
||||
//Check for ids, if there is none (Unsmushed or lossless), don't call smush function
|
||||
if (
|
||||
'undefined' === typeof wp_smushit_data ||
|
||||
(wp_smushit_data.unsmushed.length === 0 &&
|
||||
wp_smushit_data.resmush.length === 0)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bulkSmush = new Smush( $(this), true, 'nextgen' );
|
||||
SmushProcess.setOnCancelCallback( () => {
|
||||
bulkSmush.cancelAjax();
|
||||
}).update( 0, bulkSmush.ids.length ).show();
|
||||
|
||||
jQuery('.wp-smush-all, .wp-smush-scan').prop('disabled', true);
|
||||
$('.wp-smush-notice.wp-smush-remaining').hide();
|
||||
|
||||
// Run bulk Smush.
|
||||
bulkSmush.run();
|
||||
})
|
||||
.on('click', '.wp-smush-trigger-nextgen-bulk', function(e){
|
||||
e.preventDefault();
|
||||
const bulkSmushButton = $('.wp-smush-nextgen-bulk');
|
||||
if ( bulkSmushButton.length ) {
|
||||
bulkSmushButton.trigger('click');
|
||||
SUI.closeNotice( 'wp-smush-ajax-notice' );
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}(window.jQuery));
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/* global ajaxurl */
|
||||
/* global wp_smush_msgs */
|
||||
|
||||
( function( $ ) {
|
||||
'use strict';
|
||||
|
||||
const s3alert = $( '#wp-smush-s3support-alert' );
|
||||
|
||||
/**
|
||||
* S3 support alert.
|
||||
*
|
||||
* @since 3.6.2 Moved from class-s3.php
|
||||
*/
|
||||
if ( s3alert.length ) {
|
||||
const noticeOptions = {
|
||||
type: 'warning',
|
||||
icon: 'info',
|
||||
dismiss: {
|
||||
show: true,
|
||||
label: wp_smush_msgs.noticeDismiss,
|
||||
tooltip: wp_smush_msgs.noticeDismissTooltip,
|
||||
},
|
||||
};
|
||||
|
||||
window.SUI.openNotice(
|
||||
'wp-smush-s3support-alert',
|
||||
s3alert.data( 'message' ),
|
||||
noticeOptions
|
||||
);
|
||||
}
|
||||
|
||||
// Dismiss S3 support alert.
|
||||
s3alert.on( 'click', 'button', () => {
|
||||
$.post( ajaxurl,
|
||||
{
|
||||
action: 'dismiss_s3support_alert',
|
||||
_ajax_nonce: window.wp_smush_msgs.nonce,
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
// Remove API message.
|
||||
$( '#wp-smush-api-message button.sui-button-icon' ).on( 'click', function( e ) {
|
||||
e.preventDefault();
|
||||
const notice = $( '#wp-smush-api-message' );
|
||||
notice.slideUp( 'slow', function() {
|
||||
notice.remove();
|
||||
} );
|
||||
$.post( ajaxurl,
|
||||
{
|
||||
action: 'hide_api_message',
|
||||
_ajax_nonce: window.wp_smush_msgs.nonce,
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
// Hide the notice after a CTA button was clicked
|
||||
function removeNotice( e ) {
|
||||
const $notice = $( e.currentTarget ).closest( '.smush-notice' );
|
||||
$notice.fadeTo( 100, 0, () =>
|
||||
$notice.slideUp( 100, () => $notice.remove() )
|
||||
);
|
||||
}
|
||||
|
||||
// Only used for the Dashboard notification for now.
|
||||
$( '.smush-notice .smush-notice-act' ).on( 'click', ( e ) => {
|
||||
removeNotice( e );
|
||||
} );
|
||||
|
||||
// Dismiss the update notice.
|
||||
$( '.wp-smush-update-info' ).on( 'click', '.notice-dismiss', ( e ) => {
|
||||
e.preventDefault();
|
||||
removeNotice( e );
|
||||
$.post( ajaxurl,
|
||||
{
|
||||
action: 'dismiss_update_info',
|
||||
_ajax_nonce: window.wp_smush_msgs.nonce,
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
function insertHubConnectNotice() {
|
||||
const mediaHubConnectNotice = $( '#smush-hub-connect-media-notice' );
|
||||
if ( ! mediaHubConnectNotice.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const headerEnd = $( '.wrap > .wp-header-end' );
|
||||
if ( headerEnd.length ) {
|
||||
headerEnd.after( mediaHubConnectNotice.show() );
|
||||
}
|
||||
}
|
||||
|
||||
insertHubConnectNotice();
|
||||
|
||||
$( '#smush-media-notification-skip' ).on( 'click', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
$.post( ajaxurl, {
|
||||
action: 'dismiss_media_hub_connect_notice',
|
||||
_ajax_nonce: window.wp_smush_msgs.nonce,
|
||||
} )
|
||||
.done( function ( response ) {
|
||||
if ( response && response.success ) {
|
||||
$( '#smush-hub-connect-media-notice' ).remove();
|
||||
}
|
||||
} )
|
||||
.fail( function () {
|
||||
console.error( 'Failed to dismiss the media hub connect notice.' );
|
||||
} );
|
||||
} );
|
||||
|
||||
}( jQuery ) );
|
||||
+558
@@ -0,0 +1,558 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
import tracker from '../utils/tracker';
|
||||
import GlobalTracking from '../global-tracking';
|
||||
|
||||
/**
|
||||
* Modals JavaScript code.
|
||||
*/
|
||||
( function() {
|
||||
'use strict';
|
||||
const onBoardingTemplateID = 'smush-onboarding-free';
|
||||
if ( ! document.getElementById( onBoardingTemplateID ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Onboarding modal.
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
WP_Smush.onboarding = {
|
||||
membership: 'free', // Assume free by default.
|
||||
onboardingModal: document.getElementById( 'smush-onboarding-dialog' ),
|
||||
settings: {
|
||||
first: true,
|
||||
last: false,
|
||||
slide: 'start',
|
||||
fields: {},
|
||||
},
|
||||
fields: {},
|
||||
contentContainer: document.getElementById( 'smush-onboarding-content' ),
|
||||
onboardingSlides: [],
|
||||
touchX: null,
|
||||
touchY: null,
|
||||
recheckImagesLink: '',
|
||||
upsellUTMClicked: false,
|
||||
|
||||
registerEventListeners() {
|
||||
document.addEventListener(
|
||||
'smush-onboarding:rendered-slide-scan_completed',
|
||||
this.progressBarAnimation.bind( this )
|
||||
);
|
||||
|
||||
// Skip setup.
|
||||
this.skipButton = this.onboardingModal.querySelector(
|
||||
'.smush-onboarding-skip-link'
|
||||
);
|
||||
if ( this.skipButton ) {
|
||||
this.skipButton.addEventListener( 'click', this.skipSetup.bind( this ) );
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Init module.
|
||||
*/
|
||||
init() {
|
||||
if ( ! this.onboardingModal ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onboardingSlides = window.onBoardingData?.slideKeys || [];
|
||||
this.fields = window.onBoardingData?.slideFields || {};
|
||||
this.settings.slide = this.onboardingSlides.length ? this.onboardingSlides[ 0 ] : 'start';
|
||||
|
||||
const dialog = document.getElementById( onBoardingTemplateID );
|
||||
|
||||
this.membership = dialog.dataset.type;
|
||||
this.recheckImagesLink = dialog.dataset.ctaUrl;
|
||||
|
||||
if ( 'false' === dialog.dataset.tracking ) {
|
||||
this.onboardingSlides.pop();
|
||||
}
|
||||
|
||||
this.registerEventListeners();
|
||||
this.renderTemplate();
|
||||
|
||||
// Show the modal.
|
||||
window.SUI.openModal(
|
||||
'smush-onboarding-dialog',
|
||||
'wpcontent',
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get swipe coordinates.
|
||||
*
|
||||
* @param {Object} e
|
||||
*/
|
||||
handleTouchStart( e ) {
|
||||
const firstTouch = e.touches[ 0 ];
|
||||
this.touchX = firstTouch.clientX;
|
||||
this.touchY = firstTouch.clientY;
|
||||
},
|
||||
|
||||
/**
|
||||
* Process swipe left/right.
|
||||
*
|
||||
* @param {Object} e
|
||||
*/
|
||||
handleTouchMove( e ) {
|
||||
if ( ! this.touchX || ! this.touchY ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const xUp = e.touches[ 0 ].clientX,
|
||||
yUp = e.touches[ 0 ].clientY,
|
||||
xDiff = this.touchX - xUp,
|
||||
yDiff = this.touchY - yUp;
|
||||
|
||||
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {
|
||||
if ( xDiff > 0 ) {
|
||||
if ( false === WP_Smush.onboarding.settings.last ) {
|
||||
WP_Smush.onboarding.next( null, 'next' );
|
||||
}
|
||||
} else if ( false === WP_Smush.onboarding.settings.first ) {
|
||||
WP_Smush.onboarding.next( null, 'prev' );
|
||||
}
|
||||
}
|
||||
|
||||
this.touchX = null;
|
||||
this.touchY = null;
|
||||
},
|
||||
|
||||
progressBarAnimation( event ) {
|
||||
const progressBar = this.onboardingModal.querySelector( '.sui-progress-bar span' );
|
||||
const progressText = this.onboardingModal.querySelector( '.sui-progress-text' );
|
||||
let start = 0;
|
||||
const duration = 3000; // 3 seconds
|
||||
const step = 10; // ms
|
||||
|
||||
if ( ! progressBar || ! progressText ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interval = setInterval( () => {
|
||||
start += step;
|
||||
const percent = Math.min( Math.round( ( start / duration ) * 100 ), 100 );
|
||||
progressBar.style.width = percent + '%';
|
||||
progressText.textContent = percent + '%';
|
||||
if ( percent >= 100 ) {
|
||||
clearInterval( interval );
|
||||
const loader = this.onboardingModal.querySelector( '.sui-icon-loader' );
|
||||
if ( loader ) {
|
||||
loader.classList.remove( 'sui-icon-loader', 'sui-loading' );
|
||||
loader.style.color = '#1ABC9C';
|
||||
loader.classList.add( 'sui-icon-check-tick' );
|
||||
}
|
||||
const scanStats = this.onboardingModal.querySelector( '.scan-stats' );
|
||||
if ( scanStats ) {
|
||||
scanStats.classList.remove( 'sui-hidden' );
|
||||
}
|
||||
}
|
||||
}, step );
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the template, register new listeners.
|
||||
*
|
||||
* @param {string} directionClass Accepts: fadeInRight, fadeInLeft, none.
|
||||
*
|
||||
* Todo: Maybe redirect to finish scan slide when users go to in progressing slide.
|
||||
*/
|
||||
renderTemplate( directionClass = 'none' ) {
|
||||
// Grab the selected value.
|
||||
this.updateCheckboxStates();
|
||||
|
||||
const template = WP_Smush.onboarding.template( onBoardingTemplateID );
|
||||
const settings = this.settings;
|
||||
settings.fields = this.fields;
|
||||
const content = template( settings );
|
||||
|
||||
if ( content ) {
|
||||
this.contentContainer.innerHTML = content;
|
||||
|
||||
if ( 'none' === directionClass ) {
|
||||
this.contentContainer.classList.add( 'loaded' );
|
||||
} else {
|
||||
this.contentContainer.classList.remove( 'loaded' );
|
||||
this.contentContainer.classList.add( directionClass );
|
||||
setTimeout( () => {
|
||||
this.contentContainer.classList.add( 'loaded' );
|
||||
this.contentContainer.classList.remove(
|
||||
directionClass
|
||||
);
|
||||
}, 600 );
|
||||
}
|
||||
}
|
||||
|
||||
this.onboardingModal.addEventListener(
|
||||
'touchstart',
|
||||
this.handleTouchStart,
|
||||
false
|
||||
);
|
||||
this.onboardingModal.addEventListener(
|
||||
'touchmove',
|
||||
this.handleTouchMove,
|
||||
false
|
||||
);
|
||||
|
||||
this.bindSubmit();
|
||||
this.toggleSkipButton();
|
||||
this.maybeHandleUpsellUTMClick();
|
||||
|
||||
const slideRendered = new CustomEvent(
|
||||
`smush-onboarding:rendered-slide-${ this.settings.slide }`,
|
||||
{
|
||||
detail: {
|
||||
settings: this.settings,
|
||||
},
|
||||
}
|
||||
);
|
||||
document.dispatchEvent( slideRendered );
|
||||
},
|
||||
|
||||
updateCheckboxStates() {
|
||||
const inputs = this.onboardingModal.querySelectorAll(
|
||||
'input[type="checkbox"]'
|
||||
);
|
||||
if ( inputs ) {
|
||||
inputs.forEach( ( checkbox ) => {
|
||||
this.fields[ checkbox.id ] = checkbox.checked;
|
||||
} );
|
||||
}
|
||||
},
|
||||
|
||||
toggleSkipButton() {
|
||||
if ( ! this.skipButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.settings.last ) {
|
||||
this.skipButton.classList.add( 'sui-hidden' );
|
||||
} else {
|
||||
this.skipButton.classList.remove( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Catch "Finish setup wizard" button click.
|
||||
*/
|
||||
bindSubmit() {
|
||||
const submitButton = this.onboardingModal.querySelector(
|
||||
'button[type="submit"]'
|
||||
);
|
||||
const self = this;
|
||||
|
||||
if ( submitButton ) {
|
||||
submitButton.addEventListener( 'click', async function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
submitButton.classList.add( 'sui-button-onload-text' );
|
||||
|
||||
// Because we are not rendering the template, we need to update the last element value.
|
||||
self.updateCheckboxStates();
|
||||
|
||||
try {
|
||||
await self.trackFinishSetupWizard();
|
||||
} catch ( err ) {}
|
||||
|
||||
const _nonce = document.getElementById(
|
||||
'smush_quick_setup_nonce'
|
||||
);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=smush_free_setup', true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if ( 200 === xhr.status ) {
|
||||
self.onFinishingSetup();
|
||||
} else {
|
||||
window.console.log(
|
||||
'Request failed. Returned status of ' +
|
||||
xhr.status
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send(
|
||||
'smush_settings=' +
|
||||
JSON.stringify( self.fields ) +
|
||||
'&_ajax_nonce=' +
|
||||
_nonce.value
|
||||
);
|
||||
} );
|
||||
}
|
||||
},
|
||||
|
||||
onFinishingSetup() {
|
||||
this.onFinish();
|
||||
if ( window.onBoardingData?.isSiteConnected ) {
|
||||
this.redirectAndStartBulkSmush();
|
||||
} else {
|
||||
this.redirectToConnectSite();
|
||||
}
|
||||
},
|
||||
|
||||
redirectAndStartBulkSmush() {
|
||||
if ( window.onBoardingData?.startBulkSmushURL ) {
|
||||
window.location.href = window.onBoardingData?.startBulkSmushURL;
|
||||
}
|
||||
},
|
||||
|
||||
onFinish() {
|
||||
window.SUI.closeModal();
|
||||
},
|
||||
|
||||
redirectToConnectSite() {
|
||||
if ( ! window.onBoardingData?.connectSiteUrl ) {
|
||||
return;
|
||||
}
|
||||
window.location.href = window.onBoardingData?.connectSiteUrl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle navigation.
|
||||
*
|
||||
* @param {Object} e
|
||||
* @param {null|string} whereTo
|
||||
*/
|
||||
next( e, whereTo = null ) {
|
||||
const index = this.onboardingSlides.indexOf( this.settings.slide );
|
||||
let newIndex = 0;
|
||||
|
||||
if ( ! whereTo ) {
|
||||
newIndex =
|
||||
null !== e && e.classList.contains( 'next' )
|
||||
? index + 1
|
||||
: index - 1;
|
||||
} else {
|
||||
newIndex = 'next' === whereTo ? index + 1 : index - 1;
|
||||
}
|
||||
|
||||
const directionClass =
|
||||
null !== e && e.classList.contains( 'next' )
|
||||
? 'fadeInRight'
|
||||
: 'fadeInLeft';
|
||||
|
||||
this.settings = {
|
||||
first: 0 === newIndex,
|
||||
last: newIndex + 1 === this.onboardingSlides.length, // length !== index
|
||||
slide: this.onboardingSlides[ newIndex ],
|
||||
fields: this.fields,
|
||||
};
|
||||
|
||||
this.renderTemplate( directionClass );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle circle navigation.
|
||||
*
|
||||
* @param {string} target
|
||||
*/
|
||||
goTo( target ) {
|
||||
const newIndex = this.onboardingSlides.indexOf( target );
|
||||
|
||||
this.settings = {
|
||||
first: 0 === newIndex,
|
||||
last: newIndex + 1 === this.onboardingSlides.length, // length !== index
|
||||
slide: target,
|
||||
fields: this.fields,
|
||||
};
|
||||
|
||||
this.renderTemplate();
|
||||
},
|
||||
|
||||
/**
|
||||
* Skip onboarding experience.
|
||||
*/
|
||||
async skipSetup() {
|
||||
const _nonce = document.getElementById( 'smush_quick_setup_nonce' );
|
||||
|
||||
this.updateCheckboxStates();
|
||||
|
||||
try {
|
||||
await this.trackSkipSetupWizard();
|
||||
} catch ( err ) {}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
'POST',
|
||||
ajaxurl + '?action=skip_smush_setup&_ajax_nonce=' + _nonce.value
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if ( 200 === xhr.status ) {
|
||||
this.onSkipSetup();
|
||||
} else {
|
||||
window.console.log(
|
||||
'Request failed. Returned status of ' + xhr.status
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
},
|
||||
|
||||
onSkipSetup() {
|
||||
this.onFinish();
|
||||
this.redirectBulkSmushPage();
|
||||
},
|
||||
|
||||
redirectBulkSmushPage() {
|
||||
const bulkSmushPage = window.wp_smush_msgs?.bulk_smush_url;
|
||||
if ( bulkSmushPage ) {
|
||||
window.location.href = bulkSmushPage;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide new features modal.
|
||||
*
|
||||
* @param e
|
||||
* @param button
|
||||
* @since 3.7.0
|
||||
* @since 3.12.2 Add a new parameter redirectUrl
|
||||
*/
|
||||
hideUpgradeModal: ( e, button ) => {
|
||||
const isRedirectRequired = '_blank' !== button?.target;
|
||||
if ( isRedirectRequired ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
button.classList.add( 'wp-smush-link-in-progress' );
|
||||
const redirectUrl = button?.href;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=hide_new_features&_ajax_nonce=' + window.wp_smush_msgs.nonce );
|
||||
xhr.onload = () => {
|
||||
window.SUI.closeModal();
|
||||
button.classList.remove( 'wp-smush-link-in-progress' );
|
||||
|
||||
const actionName = redirectUrl ? 'cta_clicked' : 'closed';
|
||||
tracker.track( 'update_modal_displayed', {
|
||||
Action: actionName,
|
||||
} );
|
||||
|
||||
if ( 200 === xhr.status ) {
|
||||
if ( redirectUrl && isRedirectRequired ) {
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
} else {
|
||||
window.console.log(
|
||||
'Request failed. Returned status of ' + xhr.status
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
},
|
||||
maybeHandleUpsellUTMClick() {
|
||||
const isConfigureSlide = 'configure' === this.settings?.slide;
|
||||
if ( ! isConfigureSlide ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const upsellLink = this.onboardingModal.querySelector( '.smush-btn-pro-upsell' );
|
||||
|
||||
if ( upsellLink ) {
|
||||
upsellLink.addEventListener( 'click', () => {
|
||||
this.upsellUTMClicked = true;
|
||||
}, { once: true } );
|
||||
}
|
||||
|
||||
this.trackProUpsellOnClick( upsellLink );
|
||||
},
|
||||
trackFinishSetupWizard() {
|
||||
return this.trackSetupWizard( window.onBoardingData?.isSiteConnected ? 'complete_wizard' : 'connect' );
|
||||
},
|
||||
trackSkipSetupWizard() {
|
||||
return this.trackSetupWizard( 'quit' );
|
||||
},
|
||||
trackSetupWizard( action ) {
|
||||
const isWizardQuit = 'quit' === action;
|
||||
const properties = {
|
||||
Action: action,
|
||||
'Quit Step': this.getQuitStep( isWizardQuit ),
|
||||
'Settings Enabled': this.getEnabledSettings( isWizardQuit ),
|
||||
'Wizard Upsell': this.upsellUTMClicked ? 'clicked_utm' : 'na',
|
||||
};
|
||||
|
||||
const allowToTrack = this.fields?.usage;
|
||||
|
||||
return tracker.setAllowToTrack( allowToTrack ).track( 'Setup Wizard New', properties );
|
||||
},
|
||||
getQuitStep( isWizardQuit ) {
|
||||
return isWizardQuit ? ( this.settings.slide || 'na' ) : 'na';
|
||||
},
|
||||
getEnabledSettings( isWizardQuit ) {
|
||||
if ( isWizardQuit ) {
|
||||
return 'na';
|
||||
}
|
||||
|
||||
const fieldMapsForTracking = this.getFieldMapsForTracking();
|
||||
const enabledSettings = [];
|
||||
|
||||
Object.entries( this.fields ).forEach( ( [ setting, enabled ] ) => {
|
||||
if ( enabled ) {
|
||||
const featureName = setting in fieldMapsForTracking ? fieldMapsForTracking[ setting ] : setting;
|
||||
enabledSettings.push( featureName );
|
||||
}
|
||||
} );
|
||||
|
||||
return enabledSettings;
|
||||
},
|
||||
getProInterests() {
|
||||
if ( 'pro' === this.membership || ! this.upsellUTMClicked.length ) {
|
||||
return 'na';
|
||||
}
|
||||
|
||||
return this.upsellUTMClicked;
|
||||
},
|
||||
getFieldMapsForTracking() {
|
||||
return {
|
||||
usage: 'tracking',
|
||||
auto: 'auto_smush',
|
||||
lossy: 'super_smush',
|
||||
strip_exif: 'strip_exif',
|
||||
compress_backup: 'compress_backup',
|
||||
lazy_load: 'lazy_load',
|
||||
};
|
||||
},
|
||||
trackProUpsellOnClick( upsellLink ) {
|
||||
if ( ! upsellLink ) {
|
||||
return;
|
||||
}
|
||||
|
||||
upsellLink.addEventListener( 'click', ( event ) => {
|
||||
const allowToTrack = this.fields?.usage;
|
||||
tracker.setAllowToTrack( allowToTrack );
|
||||
( new GlobalTracking() ).trackSetupWizardProUpsell( event?.target?.href, 'na' );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Template function (underscores based).
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
WP_Smush.onboarding.template = _.memoize( ( id ) => {
|
||||
let compiled;
|
||||
const options = {
|
||||
evaluate: /<#([\s\S]+?)#>/g,
|
||||
interpolate: /{{{([\s\S]+?)}}}/g,
|
||||
escape: /{{([^}]+?)}}(?!})/g,
|
||||
variable: 'data',
|
||||
};
|
||||
|
||||
return ( data ) => {
|
||||
_.templateSettings = options;
|
||||
compiled =
|
||||
compiled ||
|
||||
_.template( document.getElementById( id ).innerHTML );
|
||||
return compiled( data );
|
||||
};
|
||||
} );
|
||||
|
||||
window.addEventListener( 'load', () => WP_Smush.onboarding.init() );
|
||||
}() );
|
||||
+542
@@ -0,0 +1,542 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
import tracker from "../utils/tracker";
|
||||
import GlobalTracking from '../global-tracking';
|
||||
|
||||
/**
|
||||
* Modals JavaScript code.
|
||||
*/
|
||||
( function() {
|
||||
'use strict';
|
||||
|
||||
// Keep loading onboarding script for other dependencies.
|
||||
if ( document.getElementById( 'smush-onboarding-free' ) ) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Onboarding modal.
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
WP_Smush.onboarding = {
|
||||
membership: 'free', // Assume free by default.
|
||||
onboardingModal: document.getElementById( 'smush-onboarding-dialog' ),
|
||||
first_slide: 'usage',
|
||||
settings: {
|
||||
first: true,
|
||||
last: false,
|
||||
slide: 'usage',
|
||||
value: false,
|
||||
},
|
||||
selection: {
|
||||
usage: false,
|
||||
auto: true,
|
||||
lossy: true,
|
||||
strip_exif: true,
|
||||
original: true,
|
||||
preload_images: true,
|
||||
lazy_load: true,
|
||||
},
|
||||
contentContainer: document.getElementById( 'smush-onboarding-content' ),
|
||||
onboardingSlides: [
|
||||
'usage',
|
||||
'auto',
|
||||
'lossy',
|
||||
'strip_exif',
|
||||
'original',
|
||||
'preload_images',
|
||||
'lazy_load',
|
||||
],
|
||||
touchX: null,
|
||||
touchY: null,
|
||||
recheckImagesLink: '',
|
||||
proFeaturesClicked: [],
|
||||
/**
|
||||
* Init module.
|
||||
*/
|
||||
init() {
|
||||
if ( ! this.onboardingModal ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dialog = document.getElementById( 'smush-onboarding' );
|
||||
|
||||
this.membership = dialog.dataset.type;
|
||||
this.recheckImagesLink = dialog.dataset.ctaUrl;
|
||||
|
||||
if ( 'pro' !== this.membership ) {
|
||||
this.onboardingSlides = [
|
||||
'usage',
|
||||
'auto',
|
||||
'lossy',
|
||||
'strip_exif',
|
||||
'original',
|
||||
'lazy_load',
|
||||
'pro_upsell',
|
||||
];
|
||||
}
|
||||
|
||||
if ( 'false' === dialog.dataset.tracking ) {
|
||||
this.onboardingSlides.pop();
|
||||
}
|
||||
|
||||
this.renderTemplate();
|
||||
|
||||
// Skip setup.
|
||||
this.skipButton = this.onboardingModal.querySelector(
|
||||
'.smush-onboarding-skip-link'
|
||||
);
|
||||
if ( this.skipButton ) {
|
||||
this.skipButton.addEventListener( 'click', this.skipSetup.bind( this ) );
|
||||
}
|
||||
|
||||
// Show the modal.
|
||||
window.SUI.openModal(
|
||||
'smush-onboarding-dialog',
|
||||
'wpcontent',
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get swipe coordinates.
|
||||
*
|
||||
* @param {Object} e
|
||||
*/
|
||||
handleTouchStart( e ) {
|
||||
const firstTouch = e.touches[ 0 ];
|
||||
this.touchX = firstTouch.clientX;
|
||||
this.touchY = firstTouch.clientY;
|
||||
},
|
||||
|
||||
/**
|
||||
* Process swipe left/right.
|
||||
*
|
||||
* @param {Object} e
|
||||
*/
|
||||
handleTouchMove( e ) {
|
||||
if ( ! this.touchX || ! this.touchY ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const xUp = e.touches[ 0 ].clientX,
|
||||
yUp = e.touches[ 0 ].clientY,
|
||||
xDiff = this.touchX - xUp,
|
||||
yDiff = this.touchY - yUp;
|
||||
|
||||
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {
|
||||
if ( xDiff > 0 ) {
|
||||
if ( false === WP_Smush.onboarding.settings.last ) {
|
||||
WP_Smush.onboarding.next( null, 'next' );
|
||||
}
|
||||
} else if ( false === WP_Smush.onboarding.settings.first ) {
|
||||
WP_Smush.onboarding.next( null, 'prev' );
|
||||
}
|
||||
}
|
||||
|
||||
this.touchX = null;
|
||||
this.touchY = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the template, register new listeners.
|
||||
*
|
||||
* @param {string} directionClass Accepts: fadeInRight, fadeInLeft, none.
|
||||
*/
|
||||
renderTemplate( directionClass = 'none' ) {
|
||||
this.updateCheckboxStates();
|
||||
|
||||
const template = WP_Smush.onboarding.template( 'smush-onboarding' );
|
||||
const content = template( this.settings );
|
||||
|
||||
if ( content ) {
|
||||
this.contentContainer.innerHTML = content;
|
||||
|
||||
if ( 'none' === directionClass ) {
|
||||
this.contentContainer.classList.add( 'loaded' );
|
||||
} else {
|
||||
this.contentContainer.classList.remove( 'loaded' );
|
||||
this.contentContainer.classList.add( directionClass );
|
||||
setTimeout( () => {
|
||||
this.contentContainer.classList.add( 'loaded' );
|
||||
this.contentContainer.classList.remove(
|
||||
directionClass
|
||||
);
|
||||
}, 600 );
|
||||
}
|
||||
}
|
||||
|
||||
this.onboardingModal.addEventListener(
|
||||
'touchstart',
|
||||
this.handleTouchStart,
|
||||
false
|
||||
);
|
||||
this.onboardingModal.addEventListener(
|
||||
'touchmove',
|
||||
this.handleTouchMove,
|
||||
false
|
||||
);
|
||||
|
||||
this.bindSubmit();
|
||||
this.toggleSkipButton();
|
||||
this.maybeHandleProFeatureClick();
|
||||
},
|
||||
|
||||
updateCheckboxStates() {
|
||||
// Grab the selected value.
|
||||
const input = this.onboardingModal.querySelector(
|
||||
'input[type="checkbox"]'
|
||||
);
|
||||
if ( input ) {
|
||||
this.selection[ input.id ] = input.checked;
|
||||
}
|
||||
},
|
||||
|
||||
toggleSkipButton() {
|
||||
if ( ! this.skipButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.settings.last ) {
|
||||
this.skipButton.classList.add( 'sui-hidden' );
|
||||
} else {
|
||||
this.skipButton.classList.remove( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Catch "Finish setup wizard" button click.
|
||||
*/
|
||||
bindSubmit() {
|
||||
const submitButton = this.onboardingModal.querySelector(
|
||||
'button[type="submit"]'
|
||||
);
|
||||
const self = this;
|
||||
|
||||
if ( submitButton ) {
|
||||
submitButton.addEventListener( 'click', async function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
submitButton.classList.add( 'sui-button-onload-text' );
|
||||
|
||||
// Because we are not rendering the template, we need to update the last element value.
|
||||
self.updateCheckboxStates();
|
||||
|
||||
try {
|
||||
await self.trackFinishSetupWizard();
|
||||
} catch ( err ) {
|
||||
// Do nothing..
|
||||
}
|
||||
|
||||
const _nonce = document.getElementById(
|
||||
'smush_quick_setup_nonce'
|
||||
);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=smush_setup', true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if ( 200 === xhr.status ) {
|
||||
self.onFinishingSetup();
|
||||
} else {
|
||||
window.console.log(
|
||||
'Request failed. Returned status of ' +
|
||||
xhr.status
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send(
|
||||
'smush_settings=' +
|
||||
JSON.stringify( self.selection ) +
|
||||
'&_ajax_nonce=' +
|
||||
_nonce.value
|
||||
);
|
||||
} );
|
||||
}
|
||||
},
|
||||
|
||||
onFinishingSetup() {
|
||||
this.onFinish();
|
||||
this.startRecheckImages();
|
||||
},
|
||||
|
||||
onFinish() {
|
||||
window.SUI.closeModal();
|
||||
},
|
||||
|
||||
startRecheckImages() {
|
||||
if ( ! this.recheckImagesLink ) {
|
||||
return;
|
||||
}
|
||||
window.location.href = this.recheckImagesLink;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle navigation.
|
||||
*
|
||||
* @param {Object} e
|
||||
* @param {null|string} whereTo
|
||||
*/
|
||||
next( e, whereTo = null ) {
|
||||
const index = this.onboardingSlides.indexOf( this.settings.slide );
|
||||
let newIndex = 0;
|
||||
|
||||
if ( ! whereTo ) {
|
||||
newIndex =
|
||||
null !== e && e.classList.contains( 'next' )
|
||||
? index + 1
|
||||
: index - 1;
|
||||
} else {
|
||||
newIndex = 'next' === whereTo ? index + 1 : index - 1;
|
||||
}
|
||||
|
||||
const directionClass =
|
||||
null !== e && e.classList.contains( 'next' )
|
||||
? 'fadeInRight'
|
||||
: 'fadeInLeft';
|
||||
|
||||
this.settings = {
|
||||
first: 0 === newIndex,
|
||||
last: newIndex + 1 === this.onboardingSlides.length, // length !== index
|
||||
slide: this.onboardingSlides[ newIndex ],
|
||||
value: this.selection[ this.onboardingSlides[ newIndex ] ],
|
||||
};
|
||||
|
||||
this.renderTemplate( directionClass );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle circle navigation.
|
||||
*
|
||||
* @param {string} target
|
||||
*/
|
||||
goTo( target ) {
|
||||
const newIndex = this.onboardingSlides.indexOf( target );
|
||||
|
||||
this.settings = {
|
||||
first: 0 === newIndex,
|
||||
last: newIndex + 1 === this.onboardingSlides.length, // length !== index
|
||||
slide: target,
|
||||
value: this.selection[ target ],
|
||||
};
|
||||
|
||||
this.renderTemplate();
|
||||
},
|
||||
|
||||
/**
|
||||
* Skip onboarding experience.
|
||||
*/
|
||||
async skipSetup() {
|
||||
const _nonce = document.getElementById( 'smush_quick_setup_nonce' );
|
||||
this.updateCheckboxStates();
|
||||
|
||||
// Track skip setup wizard.
|
||||
try {
|
||||
await this.trackSkipSetupWizard();
|
||||
} catch ( err ) {
|
||||
// Do nothing..
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
'POST',
|
||||
ajaxurl + '?action=skip_smush_setup&_ajax_nonce=' + _nonce.value
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if ( 200 === xhr.status ) {
|
||||
this.onSkipSetup();
|
||||
} else {
|
||||
window.console.log(
|
||||
'Request failed. Returned status of ' + xhr.status
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
},
|
||||
|
||||
onSkipSetup() {
|
||||
this.onFinish();
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide new features modal.
|
||||
* @since 3.7.0
|
||||
* @since 3.12.2 Add a new parameter redirectUrl
|
||||
*/
|
||||
hideUpgradeModal: ( e, button ) => {
|
||||
const isRedirectRequired = '_blank' !== button?.target;
|
||||
if ( isRedirectRequired ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
button.classList.add( 'wp-smush-link-in-progress' );
|
||||
const redirectUrl = button?.href;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=hide_new_features&_ajax_nonce=' + window.wp_smush_msgs.nonce );
|
||||
xhr.onload = () => {
|
||||
window.SUI.closeModal();
|
||||
button.classList.remove( 'wp-smush-link-in-progress' );
|
||||
|
||||
const actionName = redirectUrl ? 'cta_clicked' : 'closed';
|
||||
tracker.track( 'update_modal_displayed', {
|
||||
Action: actionName,
|
||||
} );
|
||||
|
||||
if ( 200 === xhr.status ) {
|
||||
if ( redirectUrl && isRedirectRequired ) {
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
} else {
|
||||
window.console.log(
|
||||
'Request failed. Returned status of ' + xhr.status
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
},
|
||||
maybeHandleProFeatureClick() {
|
||||
const isProUpsellSlide = 'pro_upsell' === this.settings?.slide;
|
||||
if ( ! isProUpsellSlide ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.upsellButton = this.onboardingModal.querySelector( '.smush-btn-pro-upsell' );
|
||||
const proFeatureToggleContainer = this.onboardingModal.querySelector( '.sui-field-list' );
|
||||
|
||||
if ( proFeatureToggleContainer ) {
|
||||
proFeatureToggleContainer.addEventListener( 'click', ( event ) => {
|
||||
const proFeatureClicked = event.target.matches( 'label' ) || event.target.closest( '.sui-toggle' );
|
||||
if ( proFeatureClicked ) {
|
||||
const featureName = event.target.closest( '.sui-field-list-item' ).querySelector( 'input[type="checkbox"]' )?.name;
|
||||
this.handleProFeatureClicked( featureName );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.maybeTrackProUpsell();
|
||||
},
|
||||
handleProFeatureClicked( featureName ) {
|
||||
this.cacheProFeatureClick( featureName );
|
||||
this.highlightUpsellButton();
|
||||
},
|
||||
highlightUpsellButton() {
|
||||
if ( ! this.upsellButton ) {
|
||||
return;
|
||||
}
|
||||
this.upsellButton.classList.remove( 'smush-btn-ripple' );
|
||||
void this.upsellButton.offsetWidth; // Trigger a reflow.
|
||||
this.upsellButton.classList.add( 'smush-btn-ripple' );
|
||||
},
|
||||
cacheProFeatureClick( proFeature ) {
|
||||
if ( ! this.proFeaturesClicked.includes( proFeature ) ) {
|
||||
this.proFeaturesClicked.push( proFeature );
|
||||
}
|
||||
},
|
||||
trackFinishSetupWizard() {
|
||||
return this.trackSetupWizard( 'finish' );
|
||||
},
|
||||
trackSkipSetupWizard() {
|
||||
return this.trackSetupWizard( 'quit' );
|
||||
},
|
||||
trackSetupWizard( action ) {
|
||||
const quitWizard = 'quit' === action;
|
||||
const properties = {
|
||||
Action: quitWizard ? 'quit' : 'finish',
|
||||
'Quit Step': this.getQuitStep( quitWizard ),
|
||||
'Settings Enabled': this.getEnabledSettings( quitWizard ),
|
||||
'Pro Interests': this.getProInterests(),
|
||||
};
|
||||
|
||||
const allowToTrack = this.selection?.usage;
|
||||
|
||||
return tracker.setAllowToTrack( allowToTrack ).track( 'Setup Wizard', properties );
|
||||
},
|
||||
getQuitStep( quitWizard ) {
|
||||
if ( ! quitWizard ) {
|
||||
return 'na';
|
||||
}
|
||||
|
||||
const fieldMapsForTracking = this.getFieldMapsForTracking();
|
||||
const setting = this.settings.slide;
|
||||
|
||||
return setting in fieldMapsForTracking ? fieldMapsForTracking[ setting ] : 'na';
|
||||
},
|
||||
getEnabledSettings( quitWizard ) {
|
||||
if ( quitWizard ) {
|
||||
return 'na';
|
||||
}
|
||||
|
||||
const fieldMapsForTracking = this.getFieldMapsForTracking();
|
||||
const enabledSettings = [];
|
||||
|
||||
Object.entries( this.selection ).forEach( ( [ setting, enabled ] ) => {
|
||||
if ( enabled ) {
|
||||
const featureName = setting in fieldMapsForTracking ? fieldMapsForTracking[ setting ] : setting;
|
||||
enabledSettings.push( featureName );
|
||||
}
|
||||
} );
|
||||
|
||||
return enabledSettings;
|
||||
},
|
||||
getProInterests() {
|
||||
if ( 'pro' === this.membership || ! this.proFeaturesClicked.length ) {
|
||||
return 'na';
|
||||
}
|
||||
|
||||
return this.proFeaturesClicked;
|
||||
},
|
||||
getFieldMapsForTracking() {
|
||||
return {
|
||||
usage: 'tracking',
|
||||
auto: 'auto_smush',
|
||||
lossy: 'free' === this.membership ? 'super_smush' : 'ultra_smush',
|
||||
strip_exif: 'strip_exif',
|
||||
original: 'full_size',
|
||||
lazy_load: 'lazy_load',
|
||||
pro_upsell: 'upgrade',
|
||||
preload_images: 'preload_images',
|
||||
};
|
||||
},
|
||||
maybeTrackProUpsell() {
|
||||
if ( ! this.upsellButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.upsellButton.addEventListener( 'click', ( event ) => {
|
||||
const allowToTrack = this.selection?.usage;
|
||||
tracker.setAllowToTrack( allowToTrack );
|
||||
( new GlobalTracking() ).trackSetupWizardProUpsell( event?.target?.href, this.getProInterests() );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Template function (underscores based).
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
WP_Smush.onboarding.template = _.memoize( ( id ) => {
|
||||
let compiled;
|
||||
const options = {
|
||||
evaluate: /<#([\s\S]+?)#>/g,
|
||||
interpolate: /{{{([\s\S]+?)}}}/g,
|
||||
escape: /{{([^}]+?)}}(?!})/g,
|
||||
variable: 'data',
|
||||
};
|
||||
|
||||
return ( data ) => {
|
||||
_.templateSettings = options;
|
||||
compiled =
|
||||
compiled ||
|
||||
_.template( document.getElementById( id ).innerHTML );
|
||||
data.first_slide = WP_Smush.onboarding.first_slide;
|
||||
return compiled( data );
|
||||
};
|
||||
} );
|
||||
|
||||
window.addEventListener( 'load', () => WP_Smush.onboarding.init() );
|
||||
}() );
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
// Review Prompts
|
||||
import Tracker from '../utils/tracker';
|
||||
import Fetcher from '../utils/fetcher';
|
||||
|
||||
class ReviewPrompts {
|
||||
#dismissNoticeKey = 'review-prompts';
|
||||
constructor() {
|
||||
document.addEventListener( 'DOMContentLoaded', this.init.bind( this ) );
|
||||
}
|
||||
|
||||
init() {
|
||||
this.reviewPromptsElement = document.getElementById( 'smush-review-prompts-notice' );
|
||||
if ( ! this.reviewPromptsElement ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.remindLater = this.reviewPromptsElement.querySelector( '#smush-review-prompts-remind-later' );
|
||||
this.alreadyDid = this.reviewPromptsElement.querySelector( '#smush-review-prompts-already-did' );
|
||||
this.rateLink = this.reviewPromptsElement.querySelector( '.button-primary' );
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
if ( this.alreadyDid ) {
|
||||
this.alreadyDid.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
this.handleDismissNotice();
|
||||
this.trackRateNoticeEvent( 'dismiss' );
|
||||
} );
|
||||
}
|
||||
|
||||
if ( this.remindLater ) {
|
||||
this.remindLater.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
this.handleRemindLater();
|
||||
this.trackRateNoticeEvent( 'remind_later' );
|
||||
} );
|
||||
}
|
||||
|
||||
if ( this.rateLink ) {
|
||||
this.rateLink.addEventListener( 'click', () => {
|
||||
this.handleDismissNotice();
|
||||
this.trackRateNoticeEvent( 'rate' );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
handleDismissNotice() {
|
||||
this.alreadyDid.classList.add( 'wp-smush-link-in-progress' );
|
||||
return Fetcher.common.dismissNotice( this.#dismissNoticeKey ).then( ( res ) => {
|
||||
if ( res.success ) {
|
||||
this.reviewPromptsElement.style.display = 'none';
|
||||
} else {
|
||||
window.WP_Smush?.helpers.showNotice( res );
|
||||
}
|
||||
} ).catch( ( error ) => {
|
||||
window.WP_Smush?.helpers.showNotice( error );
|
||||
} ).finally( () => {
|
||||
this.alreadyDid.classList.remove( 'wp-smush-link-in-progress' );
|
||||
} );
|
||||
}
|
||||
|
||||
handleRemindLater() {
|
||||
this.alreadyDid.classList.add( 'wp-smush-link-in-progress' );
|
||||
|
||||
return Fetcher.common.remindReviewPrompt().then( ( res ) => {
|
||||
if ( res.success ) {
|
||||
this.reviewPromptsElement.style.display = 'none';
|
||||
} else {
|
||||
window.WP_Smush?.helpers.showNotice( res );
|
||||
}
|
||||
} ).catch( ( error ) => {
|
||||
window.WP_Smush?.helpers.showNotice( error );
|
||||
} ).finally( () => {
|
||||
this.alreadyDid.classList.remove( 'wp-smush-link-in-progress' );
|
||||
} );
|
||||
}
|
||||
|
||||
trackRateNoticeEvent( userAction ) {
|
||||
if ( userAction ) {
|
||||
const url = new URL( window.location.href );
|
||||
const page = url.searchParams.get( 'page' );
|
||||
const locationMaps = {
|
||||
smush: 'Dashboard',
|
||||
'smush-bulk': 'Bulk Smush',
|
||||
'smush-lazy-preload': 'Lazy Load',
|
||||
'smush-cdn': 'CDN',
|
||||
'smush-next-gen': 'Next-Gen Formats',
|
||||
'smush-integrations': 'Integrations',
|
||||
'smush-settings': 'Settings',
|
||||
'smush-cross-sell': 'More free Plugins',
|
||||
};
|
||||
const location = locationMaps[ page ] || 'WordPress admin';
|
||||
|
||||
Tracker.track( 'Rating Notice', {
|
||||
Action: userAction,
|
||||
'Notice type': this.reviewPromptsElement.dataset.noticeType,
|
||||
Location: location
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new ReviewPrompts();
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Shared UI JS libraries. Use only what we need to keep the vendor file size smaller.
|
||||
*
|
||||
* @package
|
||||
*/
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/code-snippet' );
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/modal-dialog' );
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/notifications' );
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/select2.full' );
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/select2' );
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/tabs' );
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/upload' ); // Used on lazy load page (since 3.2.2).
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/reviews' );
|
||||
require( '@wpmudev/shared-ui/dist/js/_src/accordion' );
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import NextGen from './next-gen';
|
||||
|
||||
/**
|
||||
* Avif Class.
|
||||
*/
|
||||
class Avif extends NextGen {
|
||||
constructor() {
|
||||
super( 'avif' );
|
||||
}
|
||||
}
|
||||
|
||||
( function() {
|
||||
'use strict';
|
||||
new Avif();
|
||||
}() );
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* BLOCK: extend image block
|
||||
*/
|
||||
const { createHigherOrderComponent } = wp.compose,
|
||||
{ Fragment } = wp.element,
|
||||
{ InspectorControls } = wp.blockEditor,
|
||||
{ PanelBody } = wp.components;
|
||||
|
||||
/**
|
||||
* Transform bytes to human readable format.
|
||||
*
|
||||
* @param {number} bytes
|
||||
* @return {string} Readable size string.
|
||||
*/
|
||||
function humanFileSize( bytes ) {
|
||||
const thresh = 1024,
|
||||
units = [ 'kB', 'MB', 'GB', 'TB' ];
|
||||
|
||||
if ( Math.abs( bytes ) < thresh ) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
|
||||
let u = -1;
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while ( Math.abs( bytes ) >= thresh && u < units.length - 1 );
|
||||
|
||||
return bytes.toFixed( 1 ) + ' ' + units[ u ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Smush stats table.
|
||||
*
|
||||
* @param {number} id
|
||||
* @param {Object} stats
|
||||
* @return {*} Smush stats.
|
||||
*/
|
||||
export function smushStats( id, stats ) {
|
||||
if ( 'undefined' === typeof stats ) {
|
||||
return window.smush_vars.strings.gb.select_image;
|
||||
} else if ( 'string' === typeof stats ) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id="smush-stats"
|
||||
className="sui-smush-media smush-stats-wrapper hidden"
|
||||
style={ { display: 'block' } }
|
||||
>
|
||||
<table className="wp-smush-stats-holder">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="smush-stats-header">
|
||||
{ window.smush_vars.strings.gb.size }
|
||||
</th>
|
||||
<th className="smush-stats-header">
|
||||
{ window.smush_vars.strings.gb.savings }
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ Object.keys( stats.sizes )
|
||||
.filter( ( item ) => 0 < stats.sizes[ item ].percent )
|
||||
.map( ( item, i ) => (
|
||||
<tr key={ i }>
|
||||
<td>{ item.toUpperCase() }</td>
|
||||
<td>
|
||||
{ humanFileSize(
|
||||
stats.sizes[ item ].bytes
|
||||
) }{ ' ' }
|
||||
( { stats.sizes[ item ].percent }% )
|
||||
</td>
|
||||
</tr>
|
||||
) ) }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch image data. If image is Smushing, update in 3 seconds.
|
||||
*
|
||||
* TODO: this could be optimized not to query so much.
|
||||
*
|
||||
* @param {Object} props
|
||||
*/
|
||||
export function fetchProps( props ) {
|
||||
const image = new wp.api.models.Media( { id: props.attributes.id } ),
|
||||
smushData = props.attributes.smush;
|
||||
|
||||
image.fetch( { attribute: 'smush' } ).done( function( img ) {
|
||||
if ( 'string' === typeof img.smush ) {
|
||||
props.setAttributes( { smush: img.smush } );
|
||||
//setTimeout( () => fetch( props ), 3000 );
|
||||
} else if (
|
||||
'undefined' !== typeof img.smush &&
|
||||
( 'undefined' === typeof smushData ||
|
||||
JSON.stringify( smushData ) !== JSON.stringify( img.smush ) )
|
||||
) {
|
||||
props.setAttributes( { smush: img.smush } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the block’s edit component.
|
||||
* Receives the original block BlockEdit component and returns a new wrapped component.
|
||||
*/
|
||||
const smushStatsControl = createHigherOrderComponent( ( BlockEdit ) => {
|
||||
return ( props ) => {
|
||||
// If not image block or not selected, return unmodified block.
|
||||
if (
|
||||
'core/image' !== props.name ||
|
||||
! props.isSelected ||
|
||||
'undefined' === typeof props.attributes.id
|
||||
) {
|
||||
return (
|
||||
<Fragment>
|
||||
<BlockEdit { ...props } />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const smushData = props.attributes.smush;
|
||||
fetchProps( props );
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<BlockEdit { ...props } />
|
||||
<InspectorControls>
|
||||
<PanelBody title={ window.smush_vars.strings.gb.stats }>
|
||||
{ smushStats( props.attributes.id, smushData ) }
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
}, 'withInspectorControl' );
|
||||
|
||||
wp.hooks.addFilter(
|
||||
'editor.BlockEdit',
|
||||
'wp-smush/smush-data-control',
|
||||
smushStatsControl
|
||||
);
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
|
||||
/**
|
||||
* CDN functionality.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
( function() {
|
||||
'use strict';
|
||||
|
||||
WP_Smush.CDN = {
|
||||
cdnEnableButton: document.getElementById( 'smush-enable-cdn' ),
|
||||
cdnDisableButton: document.getElementById( 'smush-cancel-cdn' ),
|
||||
cdnStatsBox: document.querySelector( '.smush-cdn-stats' ),
|
||||
|
||||
init() {
|
||||
/**
|
||||
* Handle "Get Started" button click on disabled CDN page.
|
||||
*/
|
||||
if ( this.cdnEnableButton ) {
|
||||
this.cdnEnableButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
e.currentTarget.classList.add( 'sui-button-onload' );
|
||||
this.toggle_cdn( true );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Deactivate' button click on CDN page.
|
||||
*/
|
||||
if ( this.cdnDisableButton ) {
|
||||
this.cdnDisableButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
e.currentTarget.classList.add( 'sui-button-onload' );
|
||||
|
||||
this.toggle_cdn( false );
|
||||
} );
|
||||
}
|
||||
|
||||
this.updateStatsBox();
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle CDN.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {boolean} enable
|
||||
*/
|
||||
toggle_cdn( enable ) {
|
||||
const nonceField = document.getElementsByName(
|
||||
'wp_smush_options_nonce'
|
||||
);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=smush_toggle_cdn', true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if ( 200 === xhr.status ) {
|
||||
const res = JSON.parse( xhr.response );
|
||||
if ( 'undefined' !== typeof res.success && res.success ) {
|
||||
WP_Smush.helpers.redirectToPage( 'cdn' );
|
||||
} else if ( 'undefined' !== typeof res.data.message ) {
|
||||
WP_Smush.helpers.showErrorNotice( res.data.message );
|
||||
}
|
||||
} else {
|
||||
WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
|
||||
}
|
||||
};
|
||||
xhr.send(
|
||||
'param=' + enable + '&_ajax_nonce=' + nonceField[ 0 ].value
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the CDN stats box in summary meta box. Only fetch new data when on CDN page.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
updateStatsBox() {
|
||||
if (
|
||||
'undefined' === typeof this.cdnStatsBox ||
|
||||
! this.cdnStatsBox
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only fetch the new stats, when user is on CDN page.
|
||||
if ( ! window.location.search.includes( 'page=smush-cdn' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleElements();
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=get_cdn_stats', true );
|
||||
xhr.onload = () => {
|
||||
const endpoint_missing = xhr.status === 400 && xhr.response === '0';
|
||||
if (200 === xhr.status) {
|
||||
const res = JSON.parse(xhr.response);
|
||||
if ('undefined' !== typeof res.success && res.success) {
|
||||
this.toggleElements();
|
||||
} else if ('undefined' !== typeof res.data.message) {
|
||||
WP_Smush.helpers.showErrorNotice(res.data.message);
|
||||
}
|
||||
} else if (!endpoint_missing) {
|
||||
WP_Smush.helpers.showErrorNotice('Request failed. Returned status of ' + xhr.status);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show/hide elements during status update in the updateStatsBox()
|
||||
*
|
||||
* @since 3.1 Moved out from updateStatsBox()
|
||||
*/
|
||||
toggleElements() {
|
||||
const spinner = this.cdnStatsBox.querySelector(
|
||||
'.sui-icon-loader'
|
||||
);
|
||||
const elements = this.cdnStatsBox.querySelectorAll(
|
||||
'.wp-smush-stats > :not(.sui-icon-loader)'
|
||||
);
|
||||
|
||||
for ( let i = 0; i < elements.length; i++ ) {
|
||||
elements[ i ].classList.toggle( 'sui-hidden' );
|
||||
}
|
||||
|
||||
spinner.classList.toggle( 'sui-hidden' );
|
||||
},
|
||||
};
|
||||
|
||||
WP_Smush.CDN.init();
|
||||
} )();
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
|
||||
/**
|
||||
* Directory scanner module that will Smush images in the Directory Smush modal.
|
||||
*
|
||||
* @since 2.8.1
|
||||
*
|
||||
* @param {string|number} totalSteps
|
||||
* @param {string|number} currentStep
|
||||
* @return {Object} Scan object.
|
||||
* @class
|
||||
*/
|
||||
const DirectoryScanner = ( totalSteps, currentStep ) => {
|
||||
totalSteps = parseInt( totalSteps );
|
||||
currentStep = parseInt( currentStep );
|
||||
|
||||
let cancelling = false,
|
||||
failedItems = 0,
|
||||
skippedItems = 0;
|
||||
|
||||
const obj = {
|
||||
scan() {
|
||||
const remainingSteps = totalSteps - currentStep;
|
||||
if ( currentStep !== 0 ) {
|
||||
// Scan started on a previous page load.
|
||||
step( remainingSteps ).fail( this.showScanError );
|
||||
} else {
|
||||
jQuery
|
||||
.post( ajaxurl, {
|
||||
action: 'directory_smush_start',
|
||||
_ajax_nonce: window.wp_smush_msgs.nonce
|
||||
}, () =>
|
||||
step( remainingSteps ).fail( this.showScanError )
|
||||
)
|
||||
.fail( this.showScanError );
|
||||
}
|
||||
},
|
||||
|
||||
cancel() {
|
||||
cancelling = true;
|
||||
return jQuery.post( ajaxurl, {
|
||||
action: 'directory_smush_cancel',
|
||||
_ajax_nonce: window.wp_smush_msgs.nonce
|
||||
} );
|
||||
},
|
||||
|
||||
getProgress() {
|
||||
if ( cancelling ) {
|
||||
return 0;
|
||||
}
|
||||
// O M G ... Logic at it's finest!
|
||||
const remainingSteps = totalSteps - currentStep;
|
||||
return Math.min(
|
||||
Math.round(
|
||||
( parseInt( totalSteps - remainingSteps ) * 100 ) /
|
||||
totalSteps
|
||||
),
|
||||
99
|
||||
);
|
||||
},
|
||||
|
||||
onFinishStep( progress ) {
|
||||
jQuery( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
|
||||
currentStep -
|
||||
failedItems +
|
||||
'/' +
|
||||
totalSteps +
|
||||
' ' +
|
||||
window.wp_smush_msgs.progress_smushed
|
||||
);
|
||||
WP_Smush.directory.updateProgressBar( progress );
|
||||
},
|
||||
|
||||
onFinish() {
|
||||
WP_Smush.directory.updateProgressBar( 100 );
|
||||
const directorySmushUrl = `${ self.wp_smush_msgs.bulk_smush_url }&smush__directory-scan=done#directory_smush-settings-row`;
|
||||
if ( window.location.href === directorySmushUrl ) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
window.location.href = directorySmushUrl;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays an error when the scan request fails.
|
||||
*
|
||||
* @param {Object} res XHR object.
|
||||
*/
|
||||
showScanError( res ) {
|
||||
const dialog = jQuery( '#wp-smush-progress-dialog' );
|
||||
|
||||
// Add the error class to show/hide elements in the dialog.
|
||||
dialog
|
||||
.removeClass( 'wp-smush-exceed-limit' )
|
||||
.addClass( 'wp-smush-scan-error' );
|
||||
|
||||
// Add the error status and description to the error message.
|
||||
dialog
|
||||
.find( '#smush-scan-error' )
|
||||
.text( `${ res.status } ${ res.statusText }` );
|
||||
|
||||
// Show/hide the 403 error specific instructions.
|
||||
const forbiddenMessage = dialog.find( '.smush-403-error-message' );
|
||||
if ( 403 !== res.status ) {
|
||||
forbiddenMessage.addClass( 'sui-hidden' );
|
||||
} else {
|
||||
forbiddenMessage.removeClass( 'sui-hidden' );
|
||||
}
|
||||
},
|
||||
|
||||
limitReached() {
|
||||
const dialog = jQuery( '#wp-smush-progress-dialog' );
|
||||
|
||||
dialog.addClass( 'wp-smush-exceed-limit' );
|
||||
dialog
|
||||
.find( '#cancel-directory-smush' )
|
||||
.attr( 'data-tooltip', window.wp_smush_msgs.bulk_resume );
|
||||
dialog
|
||||
.find( '.sui-box-body .sui-icon-close' )
|
||||
.removeClass( 'sui-icon-close' )
|
||||
.addClass( 'sui-icon-play' );
|
||||
dialog
|
||||
.find( '#cancel-directory-smush' )
|
||||
.attr( 'id', 'cancel-directory-smush-disabled' );
|
||||
},
|
||||
|
||||
resume() {
|
||||
const dialog = jQuery( '#wp-smush-progress-dialog' );
|
||||
const resume = dialog.find( '#cancel-directory-smush-disabled' );
|
||||
|
||||
dialog.removeClass( 'wp-smush-exceed-limit' );
|
||||
dialog
|
||||
.find( '.sui-box-body .sui-icon-play' )
|
||||
.removeClass( 'sui-icon-play' )
|
||||
.addClass( 'sui-icon-close' );
|
||||
resume.attr( 'data-tooltip', 'Cancel' );
|
||||
resume.attr( 'id', 'cancel-directory-smush' );
|
||||
|
||||
obj.scan();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a scan step recursively
|
||||
*
|
||||
* Private to avoid overriding
|
||||
*
|
||||
* @param {number} remainingSteps
|
||||
*/
|
||||
const step = function( remainingSteps ) {
|
||||
if ( remainingSteps >= 0 ) {
|
||||
currentStep = totalSteps - remainingSteps;
|
||||
return jQuery.post(
|
||||
ajaxurl,
|
||||
{
|
||||
action: 'directory_smush_check_step',
|
||||
_ajax_nonce: window.wp_smush_msgs.nonce,
|
||||
step: currentStep,
|
||||
},
|
||||
( response ) => {
|
||||
// We're good - continue on.
|
||||
if (
|
||||
'undefined' !== typeof response.success &&
|
||||
response.success
|
||||
) {
|
||||
if (
|
||||
'undefined' !== typeof response.data &&
|
||||
'undefined' !== typeof response.data.skipped &&
|
||||
true === response.data.skipped
|
||||
) {
|
||||
skippedItems++;
|
||||
}
|
||||
|
||||
currentStep++;
|
||||
remainingSteps = remainingSteps - 1;
|
||||
obj.onFinishStep( obj.getProgress() );
|
||||
step( remainingSteps ).fail( obj.showScanError );
|
||||
} else if (
|
||||
'undefined' !== typeof response.data.error &&
|
||||
'dir_smush_limit_exceeded' === response.data.error
|
||||
) {
|
||||
// Limit reached. Stop.
|
||||
obj.limitReached();
|
||||
} else {
|
||||
// Error? never mind, continue, but count them.
|
||||
failedItems++;
|
||||
currentStep++;
|
||||
remainingSteps = remainingSteps - 1;
|
||||
obj.onFinishStep( obj.getProgress() );
|
||||
step( remainingSteps ).fail( obj.showScanError );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return jQuery.post(
|
||||
ajaxurl,
|
||||
{
|
||||
action: 'directory_smush_finish',
|
||||
_ajax_nonce: window.wp_smush_msgs.nonce,
|
||||
items: totalSteps - ( failedItems + skippedItems ),
|
||||
failed: failedItems,
|
||||
skipped: skippedItems,
|
||||
},
|
||||
( response ) => obj.onFinish( response )
|
||||
);
|
||||
};
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
export default DirectoryScanner;
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
|
||||
/**
|
||||
* Lazy loading functionality.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
( function() {
|
||||
'use strict';
|
||||
|
||||
WP_Smush.Lazyload = {
|
||||
lazyloadEnableButton: document.getElementById(
|
||||
'smush-enable-lazyload'
|
||||
),
|
||||
lazyloadDisableButton: document.getElementById(
|
||||
'smush-cancel-lazyload'
|
||||
),
|
||||
lazyloadIframeCheckbox: document.getElementById( 'format-iframe' ),
|
||||
init() {
|
||||
const self = this;
|
||||
|
||||
/**
|
||||
* Handle "Activate" button click on disabled Lazy load page.
|
||||
*/
|
||||
if ( this.lazyloadEnableButton ) {
|
||||
this.lazyloadEnableButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
e.currentTarget.classList.add( 'sui-button-onload' );
|
||||
|
||||
this.toggle_lazy_load( true );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Deactivate' button click on Lazy load page.
|
||||
*/
|
||||
if ( this.lazyloadDisableButton ) {
|
||||
this.lazyloadDisableButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
e.currentTarget.classList.add( 'sui-button-onload' );
|
||||
|
||||
this.toggle_lazy_load( false );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Remove icon" button click on Lazy load page.
|
||||
*
|
||||
* This removes the image from the upload placeholder.
|
||||
*
|
||||
* @since 3.2.2
|
||||
*/
|
||||
const removeSpinner = document.getElementById(
|
||||
'smush-remove-spinner'
|
||||
);
|
||||
if ( removeSpinner ) {
|
||||
removeSpinner.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
this.removeLoaderIcon();
|
||||
} );
|
||||
}
|
||||
const removePlaceholder = document.getElementById(
|
||||
'smush-remove-placeholder'
|
||||
);
|
||||
if ( removePlaceholder ) {
|
||||
removePlaceholder.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
this.removeLoaderIcon( 'placeholder' );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Remove" icon click.
|
||||
*
|
||||
* This removes the select icon from the list (not same as above functions).
|
||||
*
|
||||
* @since 3.2.2
|
||||
*/
|
||||
const items = document.querySelectorAll( '.smush-ll-remove' );
|
||||
if ( items && 0 < items.length ) {
|
||||
items.forEach( function( el ) {
|
||||
el.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
e.target.closest( 'li' ).style.display = 'none';
|
||||
self.remove(
|
||||
e.target.dataset.id,
|
||||
e.target.dataset.type
|
||||
);
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
this.handlePredefinedPlaceholders();
|
||||
this.handleEmbedVideosBoxVisibility();
|
||||
},
|
||||
|
||||
handleEmbedVideosBoxVisibility() {
|
||||
if ( ! this.lazyloadIframeCheckbox ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const embedVideosBox = document.querySelector( '.lazyload-embed-videos' );
|
||||
if ( ! embedVideosBox ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lazyloadIframeCheckbox.addEventListener( 'click', function() {
|
||||
if ( true === this.checked ) {
|
||||
embedVideosBox.classList.remove( 'sui-hidden' );
|
||||
} else {
|
||||
embedVideosBox.classList.add( 'sui-hidden' );
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle background color changes for the two predefined placeholders.
|
||||
*
|
||||
* @since 3.7.1
|
||||
*/
|
||||
handlePredefinedPlaceholders() {
|
||||
const pl1 = document.getElementById( 'placeholder-icon-1' );
|
||||
if ( pl1 ) {
|
||||
pl1.addEventListener( 'click', () => this.changeColor( '#F3F3F3' ) );
|
||||
}
|
||||
|
||||
const pl2 = document.getElementById( 'placeholder-icon-2' );
|
||||
if ( pl2 ) {
|
||||
pl2.addEventListener( 'click', () => this.changeColor( '#333333' ) );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set color.
|
||||
*
|
||||
* @since 3.7.1
|
||||
* @param {string} color
|
||||
*/
|
||||
changeColor( color ) {
|
||||
document.getElementById( 'smush-color-picker' ).value = color;
|
||||
document.querySelector( '.sui-colorpicker-hex .sui-colorpicker-value > span > span' ).style.backgroundColor = color;
|
||||
document.querySelector( '.sui-colorpicker-hex .sui-colorpicker-value > input' ).value = color;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle lazy loading.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*
|
||||
* @param {string} enable
|
||||
*/
|
||||
toggle_lazy_load( enable ) {
|
||||
const nonceField = document.getElementsByName(
|
||||
'wp_smush_options_nonce'
|
||||
);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
'POST',
|
||||
ajaxurl + '?action=smush_toggle_lazy_load',
|
||||
true
|
||||
);
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.onload = () => {
|
||||
if ( 200 === xhr.status ) {
|
||||
const res = JSON.parse( xhr.response );
|
||||
if ( 'undefined' !== typeof res.success && res.success ) {
|
||||
WP_Smush.helpers.redirectToPage( 'lazy-preload' );
|
||||
} else if ( 'undefined' !== typeof res.data.message ) {
|
||||
WP_Smush.helpers.showErrorNotice( res.data.message );
|
||||
document.querySelector( '.sui-button-onload' ).classList.remove( 'sui-button-onload' );
|
||||
}
|
||||
} else {
|
||||
WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
|
||||
document.querySelector( '.sui-button-onload' ).classList.remove( 'sui-button-onload' );
|
||||
}
|
||||
};
|
||||
xhr.send(
|
||||
'param=' + enable + '&_ajax_nonce=' + nonceField[ 0 ].value
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add lazy load spinner icon.
|
||||
*
|
||||
* @since 3.2.2
|
||||
* @param {string} type Accepts: spinner, placeholder.
|
||||
*/
|
||||
addLoaderIcon( type = 'spinner' ) {
|
||||
let frame;
|
||||
|
||||
// If the media frame already exists, reopen it.
|
||||
if ( frame ) {
|
||||
frame.open();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new media frame
|
||||
frame = wp.media( {
|
||||
title: 'Select or upload an icon',
|
||||
button: {
|
||||
text: 'Select icon',
|
||||
},
|
||||
multiple: false, // Set to true to allow multiple files to be selected
|
||||
} );
|
||||
|
||||
// When an image is selected in the media frame...
|
||||
frame.on( 'select', function() {
|
||||
// Get media attachment details from the frame state
|
||||
const attachment = frame
|
||||
.state()
|
||||
.get( 'selection' )
|
||||
.first()
|
||||
.toJSON();
|
||||
|
||||
// Send the attachment URL to our custom image input field.
|
||||
const imageIcon = document.getElementById(
|
||||
'smush-' + type + '-icon-preview'
|
||||
);
|
||||
imageIcon.style.backgroundImage =
|
||||
'url("' + attachment.url + '")';
|
||||
imageIcon.style.display = 'block';
|
||||
|
||||
// Send the attachment id to our hidden input
|
||||
document
|
||||
.getElementById( 'smush-' + type + '-icon-file' )
|
||||
.setAttribute( 'value', attachment.id );
|
||||
|
||||
// Hide the add image link
|
||||
document.getElementById(
|
||||
'smush-upload-' + type
|
||||
).style.display = 'none';
|
||||
|
||||
// Unhide the remove image link
|
||||
const removeDiv = document.getElementById(
|
||||
'smush-remove-' + type
|
||||
);
|
||||
removeDiv.querySelector( 'span' ).innerHTML =
|
||||
attachment.filename;
|
||||
removeDiv.style.display = 'block';
|
||||
} );
|
||||
|
||||
// Finally, open the modal on click
|
||||
frame.open();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove lazy load spinner icon.
|
||||
*
|
||||
* @since 3.2.2
|
||||
* @param {string} type Accepts: spinner, placeholder.
|
||||
*/
|
||||
removeLoaderIcon: ( type = 'spinner' ) => {
|
||||
// Clear out the preview image
|
||||
const imageIcon = document.getElementById(
|
||||
'smush-' + type + '-icon-preview'
|
||||
);
|
||||
imageIcon.style.backgroundImage = '';
|
||||
imageIcon.style.display = 'none';
|
||||
|
||||
// Un-hide the add image link
|
||||
document.getElementById( 'smush-upload-' + type ).style.display =
|
||||
'block';
|
||||
|
||||
// Hide the delete image link
|
||||
document.getElementById( 'smush-remove-' + type ).style.display =
|
||||
'none';
|
||||
|
||||
// Delete the image id from the hidden input
|
||||
document
|
||||
.getElementById( 'smush-' + type + '-icon-file' )
|
||||
.setAttribute( 'value', '' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove item.
|
||||
*
|
||||
* @param {number} id Image ID.
|
||||
* @param {string} type Accepts: spinner, placeholder.
|
||||
*/
|
||||
remove: ( id, type = 'spinner' ) => {
|
||||
const nonceField = document.getElementsByName(
|
||||
'wp_smush_options_nonce'
|
||||
);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=smush_remove_icon', true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.send(
|
||||
'id=' +
|
||||
id +
|
||||
'&type=' +
|
||||
type +
|
||||
'&_ajax_nonce=' +
|
||||
nonceField[ 0 ].value
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
WP_Smush.Lazyload.init();
|
||||
} )();
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/* global smush_vars */
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Adds a Smush Now button and displays stats in Media Attachment Details Screen
|
||||
*/
|
||||
(function ($, _) {
|
||||
'use strict';
|
||||
|
||||
// Local reference to the WordPress media namespace.
|
||||
const smushMedia = wp.media,
|
||||
sharedTemplate =
|
||||
"<span class='setting smush-stats' data-setting='smush'>" +
|
||||
"<span class='name'><%= label %></span>" +
|
||||
"<span class='value'><%= value %></span>" +
|
||||
'</span>',
|
||||
template = _.template(sharedTemplate);
|
||||
|
||||
/**
|
||||
* Create the template.
|
||||
*
|
||||
* @param {string} smushHTML
|
||||
* @return {Object} Template object
|
||||
*/
|
||||
const prepareTemplate = function (smushHTML) {
|
||||
/**
|
||||
* @param {Array} smush_vars.strings Localization strings.
|
||||
* @param {Object} smush_vars Object from wp_localize_script()
|
||||
*/
|
||||
return template({
|
||||
label: smush_vars.strings.stats_label,
|
||||
value: smushHTML,
|
||||
});
|
||||
};
|
||||
|
||||
if (
|
||||
'undefined' !== typeof smushMedia.view &&
|
||||
'undefined' !== typeof smushMedia.view.Attachment.Details.TwoColumn
|
||||
) {
|
||||
// Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
|
||||
const smushMediaTwoColumn =
|
||||
smushMedia.view.Attachment.Details.TwoColumn;
|
||||
|
||||
/**
|
||||
* Add Smush details to attachment.
|
||||
*
|
||||
* A similar view to media.view.Attachment.Details
|
||||
* for use in the Edit Attachment modal.
|
||||
*
|
||||
* @see wp-includes/js/media-grid.js
|
||||
*/
|
||||
smushMedia.view.Attachment.Details.TwoColumn = smushMediaTwoColumn.extend(
|
||||
{
|
||||
initialize() {
|
||||
smushMediaTwoColumn.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change:smush', this.render);
|
||||
},
|
||||
|
||||
render() {
|
||||
// Ensure that the main attachment fields are rendered.
|
||||
smushMedia.view.Attachment.prototype.render.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
|
||||
const smushHTML = this.model.get('smush');
|
||||
if (typeof smushHTML === 'undefined') {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.model.fetch();
|
||||
|
||||
/**
|
||||
* Detach the views, append our custom fields, make sure that our data is fully updated
|
||||
* and re-render the updated view.
|
||||
*/
|
||||
this.views.detach();
|
||||
this.$el
|
||||
.find('.settings')
|
||||
.append(prepareTemplate(smushHTML));
|
||||
this.views.render();
|
||||
|
||||
return this;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
|
||||
const smushAttachmentDetails = smushMedia.view.Attachment.Details;
|
||||
|
||||
/**
|
||||
* Add Smush details to attachment.
|
||||
*/
|
||||
smushMedia.view.Attachment.Details = smushAttachmentDetails.extend({
|
||||
initialize() {
|
||||
smushAttachmentDetails.prototype.initialize.apply(this, arguments);
|
||||
this.listenTo(this.model, 'change:smush', this.render);
|
||||
},
|
||||
|
||||
render() {
|
||||
// Ensure that the main attachment fields are rendered.
|
||||
smushMedia.view.Attachment.prototype.render.apply(this, arguments);
|
||||
|
||||
const smushHTML = this.model.get('smush');
|
||||
if (typeof smushHTML === 'undefined') {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.model.fetch();
|
||||
|
||||
/**
|
||||
* Detach the views, append our custom fields, make sure that our data is fully updated
|
||||
* and re-render the updated view.
|
||||
*/
|
||||
this.views.detach();
|
||||
this.$el.append(prepareTemplate(smushHTML));
|
||||
|
||||
return this;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a new MediaLibraryTaxonomyFilter we later will instantiate
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
const MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
|
||||
id: 'media-attachment-smush-filter',
|
||||
|
||||
createFilters() {
|
||||
this.filters = {
|
||||
all: {
|
||||
text: smush_vars.strings.filter_all,
|
||||
props: { stats: 'all' },
|
||||
priority: 10,
|
||||
},
|
||||
|
||||
unsmushed: {
|
||||
text: smush_vars.strings.filter_not_processed,
|
||||
props: { stats: 'unsmushed' },
|
||||
priority: 20,
|
||||
},
|
||||
|
||||
excluded: {
|
||||
text: smush_vars.strings.filter_excl,
|
||||
props: { stats: 'excluded' },
|
||||
priority: 30,
|
||||
},
|
||||
|
||||
failed: {
|
||||
text: smush_vars.strings.filter_failed,
|
||||
props: { stats: 'failed_processing' },
|
||||
priority: 40,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Extend and override wp.media.view.AttachmentsBrowser to include our new filter.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
const AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
|
||||
wp.media.view.AttachmentsBrowser = wp.media.view.AttachmentsBrowser.extend({
|
||||
createToolbar() {
|
||||
// Make sure to load the original toolbar
|
||||
AttachmentsBrowser.prototype.createToolbar.call(this);
|
||||
this.toolbar.set(
|
||||
'MediaLibraryTaxonomyFilter',
|
||||
new MediaLibraryTaxonomyFilter({
|
||||
controller: this.controller,
|
||||
model: this.collection.props,
|
||||
priority: -75,
|
||||
}).render()
|
||||
);
|
||||
},
|
||||
});
|
||||
})(jQuery, _);
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
/* global ajaxurl */
|
||||
/* global WP_Smush */
|
||||
|
||||
/**
|
||||
* NextGen class.
|
||||
*/
|
||||
export default class NextGen {
|
||||
constructor( nextGenFormat ) {
|
||||
this.nextGenFormat = nextGenFormat;
|
||||
this.nonceField = document.getElementsByName( 'wp_smush_options_nonce' );
|
||||
this.toggleModuleButton = document.getElementById( `smush-toggle-${ nextGenFormat }-button` );
|
||||
this.deleteAllButton = document.getElementById( `wp-smush-${ nextGenFormat }-delete-all` );
|
||||
|
||||
this.registerGlobalEvents();
|
||||
this.registerEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register global events.
|
||||
*/
|
||||
registerGlobalEvents() {
|
||||
if ( NextGen.isGlobalEventListenerAdded ) {
|
||||
return;
|
||||
}
|
||||
NextGen.isGlobalEventListenerAdded = true;
|
||||
|
||||
document.addEventListener( 'onSavedSmushSettings', this.onSavedNextGenSettingsHandler.bind( this ) );
|
||||
document.addEventListener( 'on-smush-next-gen-activated-notice', this.showNextGenActivatedModal.bind( this ) );
|
||||
document.addEventListener( 'on-smush-next-gen-conversion-changed-notice', this.showNextGenConversionChangedModal.bind( this ) );
|
||||
}
|
||||
|
||||
registerEvents() {
|
||||
this.maybeShowDeleteAllSuccessNotice();
|
||||
|
||||
/**
|
||||
* Handles the "Deactivate" and "Get Started" buttons on the Next-Gen page.
|
||||
*/
|
||||
if ( this.toggleModuleButton ) {
|
||||
this.toggleModuleButton.addEventListener( 'click', ( e ) =>
|
||||
this.toggleModule( e )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the "Delete Next-Gen images" button.
|
||||
*/
|
||||
if ( this.deleteAllButton ) {
|
||||
this.deleteAllButton.addEventListener( 'click', ( e ) => this.deleteAll( e ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Next-Gen module.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
toggleModule( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const button = e.currentTarget,
|
||||
doEnable = 'enable' === button.dataset.action;
|
||||
|
||||
button.classList.add( 'sui-button-onload' );
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + `?action=smush_${ this.nextGenFormat }_toggle`, true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
|
||||
xhr.onload = () => {
|
||||
const res = JSON.parse( xhr.response );
|
||||
|
||||
if ( 200 === xhr.status ) {
|
||||
if ( 'undefined' !== typeof res.success && res.success ) {
|
||||
const scanPromise = this.runScan();
|
||||
scanPromise.onload = () => {
|
||||
this.redirectToNextGenPage();
|
||||
};
|
||||
} else if ( 'undefined' !== typeof res.data.message ) {
|
||||
this.showNotice( res.data.message );
|
||||
button.classList.remove( 'sui-button-onload' );
|
||||
}
|
||||
} else {
|
||||
let message = window.wp_smush_msgs.generic_ajax_error;
|
||||
if ( res && 'undefined' !== typeof res.data.message ) {
|
||||
message = res.data.message;
|
||||
}
|
||||
this.showNotice( message );
|
||||
button.classList.remove( 'sui-button-onload' );
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(
|
||||
'param=' + doEnable + '&_ajax_nonce=' + this.nonceField[ 0 ].value
|
||||
);
|
||||
}
|
||||
|
||||
deleteAll( e ) {
|
||||
const button = e.currentTarget;
|
||||
button.classList.add( 'sui-button-onload' );
|
||||
|
||||
let message = false;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + `?action=smush_${ this.nextGenFormat }_delete_all`, true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
|
||||
xhr.onload = () => {
|
||||
const res = JSON.parse( xhr.response );
|
||||
if ( 200 === xhr.status ) {
|
||||
if ( 'undefined' !== typeof res.success && res.success ) {
|
||||
const scanPromise = this.runScan();
|
||||
scanPromise.onload = () => {
|
||||
location.search =
|
||||
location.search + `&smush-notice=${ this.nextGenFormat }-deleted`;
|
||||
};
|
||||
} else {
|
||||
message = window.wp_smush_msgs.generic_ajax_error;
|
||||
}
|
||||
} else {
|
||||
message = window.wp_smush_msgs.generic_ajax_error;
|
||||
}
|
||||
|
||||
if ( res && res.data && res.data.message ) {
|
||||
message = res.data.message;
|
||||
}
|
||||
|
||||
if ( message ) {
|
||||
button.classList.remove( 'sui-button-onload' );
|
||||
|
||||
const noticeMessage = `<p style="text-align: left;">${ message }</p>`;
|
||||
const noticeOptions = {
|
||||
type: 'error',
|
||||
icon: 'info',
|
||||
autoclose: {
|
||||
show: false,
|
||||
},
|
||||
};
|
||||
|
||||
window.SUI.openNotice(
|
||||
'wp-smush-next-gen-delete-all-error-notice',
|
||||
noticeMessage,
|
||||
noticeOptions
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send( '_ajax_nonce=' + this.nonceField[ 0 ].value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the scanning of images for updating the images to re-smush.
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
runScan() {
|
||||
const xhr = new XMLHttpRequest(),
|
||||
nonceField = document.getElementsByName(
|
||||
'wp_smush_options_nonce'
|
||||
);
|
||||
|
||||
xhr.open( 'POST', ajaxurl + '?action=scan_for_resmush', true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
|
||||
xhr.send( '_ajax_nonce=' + nonceField[ 0 ].value );
|
||||
|
||||
return xhr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show message (notice).
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {string} type
|
||||
*/
|
||||
showNotice( message, type ) {
|
||||
if ( 'undefined' === typeof message ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noticeMessage = `<p>${ message }</p>`;
|
||||
const noticeOptions = {
|
||||
type: type || 'error',
|
||||
icon: 'info',
|
||||
dismiss: {
|
||||
show: true,
|
||||
label: window.wp_smush_msgs.noticeDismiss,
|
||||
tooltip: window.wp_smush_msgs.noticeDismissTooltip,
|
||||
},
|
||||
autoclose: {
|
||||
show: false,
|
||||
},
|
||||
};
|
||||
|
||||
window.SUI.openNotice(
|
||||
'wp-smush-ajax-notice',
|
||||
noticeMessage,
|
||||
noticeOptions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show delete all webp success notice.
|
||||
*/
|
||||
maybeShowDeleteAllSuccessNotice() {
|
||||
const deletedAllNoticeElementID = `wp-smush-${ this.nextGenFormat }-delete-all-notice`;
|
||||
const deletedAllNoticeElement = document.getElementById( deletedAllNoticeElementID );
|
||||
if ( ! deletedAllNoticeElement ) {
|
||||
return;
|
||||
}
|
||||
const noticeMessage = `<p>${
|
||||
deletedAllNoticeElement
|
||||
.dataset.message
|
||||
}</p>`;
|
||||
|
||||
const noticeOptions = {
|
||||
type: 'success',
|
||||
icon: 'check-tick',
|
||||
dismiss: {
|
||||
show: true,
|
||||
},
|
||||
};
|
||||
|
||||
window.SUI.openNotice(
|
||||
deletedAllNoticeElementID,
|
||||
noticeMessage,
|
||||
noticeOptions
|
||||
);
|
||||
}
|
||||
|
||||
onSavedNextGenSettingsHandler( status ) {
|
||||
if ( 'next-gen' === status?.detail?.page ) {
|
||||
if ( status?.detail?.next_gen_format_changed ) {
|
||||
this.redirectToNextGenPage( '&smush-notice=next-gen-conversion-changed' );
|
||||
} else if ( status?.detail?.webp_method_changed ) {
|
||||
this.redirectToNextGenPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showNextGenActivatedModal() {
|
||||
if ( ! window.WP_Smush ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activatedModalId = 'smush-next-gen-activated-modal';
|
||||
if ( ! document.getElementById( activatedModalId ) ) {
|
||||
this.redirectToNextGenPage();
|
||||
return;
|
||||
}
|
||||
|
||||
window.WP_Smush.helpers.showModal( activatedModalId, {
|
||||
isCloseOnEsc: true,
|
||||
} );
|
||||
}
|
||||
|
||||
showNextGenConversionChangedModal() {
|
||||
if ( ! window.WP_Smush ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const conversionChangedModalId = 'smush-next-gen-conversion-changed-modal';
|
||||
if ( ! document.getElementById( conversionChangedModalId ) ) {
|
||||
this.redirectToNextGenPage();
|
||||
return;
|
||||
}
|
||||
|
||||
window.WP_Smush.helpers.showModal( conversionChangedModalId, {
|
||||
isCloseOnEsc: true,
|
||||
} );
|
||||
}
|
||||
|
||||
redirectToNextGenPage( noticeParam ) {
|
||||
window.location.href = window.wp_smush_msgs.nextGenURL + ( noticeParam || '' );
|
||||
}
|
||||
}
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
import tracker from '../utils/tracker';
|
||||
|
||||
class ProductAnalytics {
|
||||
troubleshootClicked = false;
|
||||
resumeBulkSmushCount = 0;
|
||||
missedEventsKey = 'wp_smush_missed_events';
|
||||
|
||||
init() {
|
||||
if ( ! tracker.allowToTrack() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.trackUltraLinks();
|
||||
this.trackUpsellLinks();
|
||||
|
||||
this.registerTroubleshootClickEvent();
|
||||
|
||||
// Scan Interrupted Event from Scan Modal.
|
||||
this.trackScanInterruptedEventOnStopScanningModal();
|
||||
this.trackScanInterruptedEventOnRetryScanModal();
|
||||
|
||||
// Bulk Smush Interrupted Event from Bulk Smush Modal.
|
||||
this.trackBulkSmushInterruptedEventOnStopBulkSmushModal();
|
||||
this.trackBulkSmushInterruptedEventOnRetryBulkSmushModal();
|
||||
this.registerBulkSmushResumeClickEvent();
|
||||
|
||||
// Bulk Smush Interrupted Event when exit ajax bulk smush.
|
||||
this.trackBulkSmushInterruptedEventWhenExitingAjaxBulkSmush();
|
||||
|
||||
// Interrupted Event from Inline Notice.
|
||||
this.trackInterruptedEventFromInlineNotice();
|
||||
|
||||
// Interrupted Event from Loopback Error Modal.
|
||||
this.trackInterruptedEventFromLoopbackErrorModal();
|
||||
|
||||
this.maybeTrackMissedEventsOnLoad();
|
||||
}
|
||||
|
||||
trackUltraLinks() {
|
||||
const ultraUpsellLinks = document.querySelectorAll( '.wp-smush-upsell-ultra-compression' );
|
||||
if ( ! ultraUpsellLinks ) {
|
||||
return;
|
||||
}
|
||||
const getLocation = ( ultraLink ) => {
|
||||
const locations = {
|
||||
settings: 'bulksmush_settings',
|
||||
dashboard: 'dash_summary',
|
||||
bulk: 'bulksmush_summary',
|
||||
directory: 'directory_summary',
|
||||
'lazy-preload': 'lazy_summary',
|
||||
cdn: 'cdn_summary',
|
||||
'next-gen': 'webp_summary',
|
||||
};
|
||||
const locationId = ultraLink.classList.contains( 'wp-smush-ultra-compression-link' ) ? 'settings' : this.getCurrentPageSlug();
|
||||
return locations[ locationId ] || 'bulksmush_settings';
|
||||
};
|
||||
|
||||
ultraUpsellLinks.forEach( ( ultraLink ) => {
|
||||
const eventName = 'ultra_upsell_modal';
|
||||
ultraLink.addEventListener( 'click', ( e ) => {
|
||||
tracker.track( eventName, {
|
||||
Location: getLocation( e.target ),
|
||||
'Modal Action': 'direct_cta',
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
trackUpsellLinks() {
|
||||
const upsellLinks = document.querySelectorAll( '[href*="utm_source=smush"]' );
|
||||
if ( ! upsellLinks ) {
|
||||
return;
|
||||
}
|
||||
upsellLinks.forEach( ( upsellLink ) => {
|
||||
upsellLink.addEventListener( 'click', ( e ) => {
|
||||
const params = new URL( e.target.href ).searchParams;
|
||||
if ( ! params ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const campaign = params.get( 'utm_campaign' );
|
||||
const upsellLocations = {
|
||||
// CDN.
|
||||
summary_cdn: 'dash_summary',
|
||||
'smush-dashboard-cdn-upsell': 'dash_widget',
|
||||
smush_bulksmush_cdn: 'bulk_smush_progress',
|
||||
smush_cdn_upgrade_button: 'cdn_page',
|
||||
smush_bulksmush_library_gif_cdn: 'media_library',
|
||||
smush_bulk_smush_complete_global: 'bulk_smush_complete',
|
||||
|
||||
// Local Next-Gen.
|
||||
'summary_next-gen': 'dash_summary',
|
||||
'smush-dashboard-next-gen-upsell': 'dash_widget',
|
||||
'smush_next-gen_upgrade_button': 'Next-Gen Formats',// Moved from React WebP - free-content.jsx
|
||||
};
|
||||
|
||||
if ( ! ( campaign in upsellLocations ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Location = upsellLocations[ campaign ];
|
||||
const matches = campaign.match( /(cdn|next-gen)/i );
|
||||
const upsellModule = matches && matches[ 0 ];
|
||||
|
||||
const eventName = 'next-gen' === upsellModule ? 'local_webp_upsell' : 'cdn_upsell';
|
||||
tracker.track( eventName, { Location } );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
trackScanInterruptedEventOnStopScanningModal() {
|
||||
const stopScanningModal = document.getElementById( 'smush-stop-scanning-dialog' );
|
||||
if ( ! stopScanningModal ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const closeButtons = stopScanningModal.querySelectorAll( '[data-modal-close]' );
|
||||
closeButtons.forEach( ( closeButton ) => {
|
||||
closeButton.addEventListener( 'click', ( e ) => {
|
||||
const action = e.target.dataset?.action || 'Close';
|
||||
this.trackScanInterruptedEvent( {
|
||||
Trigger: 'cancel_in_progress',
|
||||
'Modal Action': action,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
trackBulkSmushInterruptedEventOnStopBulkSmushModal() {
|
||||
const stopBulkSmushModal = document.getElementById( 'smush-stop-bulk-smush-modal' );
|
||||
if ( ! stopBulkSmushModal ) {
|
||||
return;
|
||||
}
|
||||
const closeButtons = stopBulkSmushModal.querySelectorAll( '[data-modal-close]' );
|
||||
closeButtons.forEach( ( closeButton ) => {
|
||||
closeButton.addEventListener( 'click', ( e ) => {
|
||||
const action = e.target.dataset?.action || 'Close';
|
||||
this.trackBulkSmushInterruptedEvent( {
|
||||
Trigger: 'cancel_in_progress',
|
||||
'Modal Action': action,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
trackScanInterruptedEventOnRetryScanModal() {
|
||||
const retryScanModal = document.getElementById( 'smush-retry-scan-notice' );
|
||||
if ( ! retryScanModal ) {
|
||||
return;
|
||||
}
|
||||
const retryButton = retryScanModal.querySelector( '.smush-retry-scan-notice-button' );
|
||||
if ( retryButton ) {
|
||||
retryButton.addEventListener( 'click', ( e ) => {
|
||||
const recheckImagesBtn = document.querySelector( '.wp-smush-scan' );
|
||||
if ( recheckImagesBtn ) {
|
||||
this.trackScanInterruptedEvent( {
|
||||
Trigger: 'failed_modal',
|
||||
'Modal Action': 'Retry',
|
||||
} );
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
const event = 'Scan Interrupted';
|
||||
const properties = this.getScanInterruptedEventProperties( {
|
||||
Trigger: 'failed_modal',
|
||||
'Modal Action': 'Retry',
|
||||
} );
|
||||
|
||||
tracker.track( event, properties ).catch( () => {
|
||||
this.cacheMissedEvent( {
|
||||
event,
|
||||
properties,
|
||||
} );
|
||||
} ).finally( () => {
|
||||
window.location.href = e.target.href;
|
||||
} );
|
||||
} );
|
||||
}
|
||||
const closeButtons = retryScanModal.querySelectorAll( '[data-modal-close]' );
|
||||
closeButtons.forEach( ( closeButton ) => {
|
||||
closeButton.addEventListener( 'click', ( e ) => {
|
||||
const action = e.target.dataset?.action || 'Close';
|
||||
this.trackScanInterruptedEvent( {
|
||||
Trigger: 'failed_modal',
|
||||
'Modal Action': action,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
trackBulkSmushInterruptedEventOnRetryBulkSmushModal() {
|
||||
const retryBulkModal = document.getElementById( 'smush-retry-bulk-smush-notice' );
|
||||
if ( ! retryBulkModal ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const retryButton = retryBulkModal.querySelector( '.smush-retry-bulk-smush-notice-button' );
|
||||
if ( retryButton ) {
|
||||
retryButton.addEventListener( 'click', () => {
|
||||
this.trackBulkSmushInterruptedEvent( {
|
||||
Trigger: 'failed_modal',
|
||||
'Modal Action': 'Retry',
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
const closeButtons = retryBulkModal.querySelectorAll( '[data-modal-close]' );
|
||||
closeButtons.forEach( ( closeButton ) => {
|
||||
closeButton.addEventListener( 'click', ( e ) => {
|
||||
const action = e.target.dataset?.action || 'Close';
|
||||
this.trackBulkSmushInterruptedEvent( {
|
||||
Trigger: 'failed_modal',
|
||||
'Modal Action': action,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
trackScanInterruptedEvent( properties ) {
|
||||
return tracker.track( 'Scan Interrupted', this.getScanInterruptedEventProperties( properties ) );
|
||||
}
|
||||
|
||||
getScanInterruptedEventProperties( properties ) {
|
||||
return Object.assign( {
|
||||
Troubleshoot: this.troubleshootClicked ? 'Yes' : 'No',
|
||||
}, properties );
|
||||
}
|
||||
|
||||
trackBulkSmushInterruptedEventWhenExitingAjaxBulkSmush() {
|
||||
if ( this.canUseBackgroundOptimization() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const progressBar = document.querySelector( '.wp-smush-bulk-progress-bar-wrapper' );
|
||||
if ( ! progressBar ) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener( 'beforeunload', async () => {
|
||||
const isBulkSmushInProgress = window.WP_Smush?.bulk.isBulkSmushInProgress() && ! progressBar.classList.contains( 'sui-hidden' );
|
||||
if ( ! isBulkSmushInProgress ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFreeExceeded = progressBar.classList.contains( 'wp-smush-exceed-limit' );
|
||||
|
||||
const event = 'Bulk Smush Interrupted';
|
||||
const properties = this.getBulkSmushInterruptedEventProperties(
|
||||
{
|
||||
Trigger: isFreeExceeded ? 'exit_50_limit' : 'exit_in_progress',
|
||||
'Modal Action': 'Exit',
|
||||
'Retry Attempts': this.resumeBulkSmushCount,
|
||||
}
|
||||
);
|
||||
|
||||
await tracker.track( event, properties ).catch( () => {
|
||||
this.cacheMissedEvent( {
|
||||
event,
|
||||
properties,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
cacheMissedEvent( eventData ) {
|
||||
if ( window.localStorage ) {
|
||||
// As now we only use it for one event, so let's keep it as a simple array.
|
||||
window.localStorage.setItem( this.missedEventsKey, JSON.stringify( [ eventData ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
getMissedEvents() {
|
||||
if ( ! window.localStorage ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const properties = window.localStorage.getItem( this.missedEventsKey );
|
||||
if ( ! properties ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return JSON.parse( properties );
|
||||
}
|
||||
|
||||
clearMissedEvents() {
|
||||
if ( window.localStorage ) {
|
||||
window.localStorage.removeItem( this.missedEventsKey );
|
||||
}
|
||||
}
|
||||
|
||||
canUseBackgroundOptimization() {
|
||||
return 'undefined' !== typeof window.wp_smushit_data?.bo_stats;
|
||||
}
|
||||
|
||||
trackBulkSmushInterruptedEvent( properties ) {
|
||||
return tracker.track( 'Bulk Smush Interrupted', this.getBulkSmushInterruptedEventProperties( properties ) );
|
||||
}
|
||||
|
||||
getBulkSmushInterruptedEventProperties( properties ) {
|
||||
return Object.assign(
|
||||
{
|
||||
Troubleshoot: this.troubleshootClicked ? 'Yes' : 'No',
|
||||
},
|
||||
this.getBulkSmushProcessStats(),
|
||||
properties
|
||||
);
|
||||
}
|
||||
|
||||
getBulkSmushProcessStats() {
|
||||
if ( this.canUseBackgroundOptimization() ) {
|
||||
// Handled it via PHP.
|
||||
return {};
|
||||
}
|
||||
const bulkSmushObject = window.WP_Smush?.bulk;
|
||||
if ( ! bulkSmushObject ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
'Retry Attempts': this.resumeBulkSmushCount,
|
||||
'Total Enqueued Images': bulkSmushObject.getTotalEnqueuedImages(),
|
||||
'Completion Percentage': bulkSmushObject.getCompletionPercentage(),
|
||||
};
|
||||
}
|
||||
|
||||
trackInterruptedEventFromInlineNotice() {
|
||||
this.trackInterruptedEventFromInlineNoticeOnDashboard();
|
||||
this.trackBulkSmushInterruptedEventFromInlineNoticeOnBulkSmush();
|
||||
this.trackScanInterruptedEventFromInlineNoticeOnBulkSmush();
|
||||
}
|
||||
|
||||
trackInterruptedEventFromInlineNoticeOnDashboard() {
|
||||
const dashboardBulkElement = document.getElementById( 'smush-box-dashboard-bulk' );
|
||||
if ( ! dashboardBulkElement ) {
|
||||
return;
|
||||
}
|
||||
this.trackBulkSmushInterruptedEventFromInlineNoticeOnDashboard( dashboardBulkElement );
|
||||
this.trackScanInterruptedEventFromInlineNoticeOnDashboard( dashboardBulkElement );
|
||||
}
|
||||
|
||||
trackBulkSmushInterruptedEventFromInlineNoticeOnDashboard( dashboardBulkElement ) {
|
||||
const triggerBulkSmushButton = dashboardBulkElement.querySelector( '.wp-smush-retry-bulk-smush-link' );
|
||||
if ( ! triggerBulkSmushButton ) {
|
||||
return;
|
||||
}
|
||||
triggerBulkSmushButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
|
||||
const event = 'Bulk Smush Interrupted';
|
||||
const properties = this.getBulkSmushInterruptedEventProperties(
|
||||
{
|
||||
Trigger: 'failed_notice',
|
||||
'Modal Action': 'Retry',
|
||||
}
|
||||
);
|
||||
|
||||
tracker.track( event, properties ).catch( () => {
|
||||
this.cacheMissedEvent( {
|
||||
event,
|
||||
properties,
|
||||
} );
|
||||
} ).finally( () => {
|
||||
window.location.href = e.target.href;
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
trackBulkSmushInterruptedInlineNoticeEvent() {
|
||||
return this.trackBulkSmushInterruptedEvent( {
|
||||
Trigger: 'failed_notice',
|
||||
'Modal Action': 'Retry',
|
||||
} );
|
||||
}
|
||||
|
||||
trackBulkSmushInterruptedEventFromInlineNoticeOnBulkSmush() {
|
||||
const triggerBulkSmushButton = document.querySelector( '.wp-smush-inline-retry-bulk-smush-notice .wp-smush-trigger-bulk-smush' );
|
||||
if ( ! triggerBulkSmushButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerBulkSmushButton.addEventListener( 'click', () => {
|
||||
this.trackBulkSmushInterruptedInlineNoticeEvent();
|
||||
} );
|
||||
}
|
||||
|
||||
trackScanInterruptedEventFromInlineNoticeOnDashboard( dashboardBulkElement ) {
|
||||
const triggerScanButton = dashboardBulkElement.querySelector( '.wp-smush-retry-scan-link' );
|
||||
if ( ! triggerScanButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerScanButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
|
||||
const event = 'Scan Interrupted';
|
||||
const properties = this.getScanInterruptedEventProperties( {
|
||||
Trigger: 'failed_notice',
|
||||
'Modal Action': 'Retry',
|
||||
} );
|
||||
|
||||
tracker.track( event, properties ).catch( () => {
|
||||
this.cacheMissedEvent( {
|
||||
event,
|
||||
properties,
|
||||
} );
|
||||
} ).finally( () => {
|
||||
window.location.href = e.target.href;
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
trackScanInterruptedEventFromInlineNoticeOnBulkSmush() {
|
||||
const recheckImagesNotice = document.querySelector( '.wp-smush-recheck-images-notice-box' );
|
||||
if ( ! recheckImagesNotice ) {
|
||||
return;
|
||||
}
|
||||
const triggerBackgroundScanImagesLink = recheckImagesNotice.querySelector( '.wp-smush-trigger-background-scan' );
|
||||
if ( triggerBackgroundScanImagesLink ) {
|
||||
triggerBackgroundScanImagesLink.addEventListener( 'click', () => {
|
||||
// We are using the same frame for failed scan notice and generic required scan notice,
|
||||
// so we need to verify the failed notice before tracking the event.
|
||||
const containTroubleshootingLink = triggerBackgroundScanImagesLink?.previousElementSibling?.querySelector( 'a' );
|
||||
if ( ! containTroubleshootingLink ) {
|
||||
return;
|
||||
}
|
||||
this.trackScanInterruptedEvent( {
|
||||
Trigger: 'failed_notice',
|
||||
'Modal Action': 'Retry',
|
||||
} );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
trackInterruptedEventFromLoopbackErrorModal() {
|
||||
const loopbackErrorModal = document.getElementById( 'smush-loopback-error-dialog' );
|
||||
if ( ! loopbackErrorModal ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loopbackErrorDocsLink = loopbackErrorModal.querySelector( 'a[href*="#loopback-request-issue"]' );
|
||||
let isTroubleshootClicked = false;
|
||||
if ( loopbackErrorDocsLink ) {
|
||||
loopbackErrorDocsLink.addEventListener( 'click', () => {
|
||||
isTroubleshootClicked = true;
|
||||
}, { once: true } );
|
||||
}
|
||||
|
||||
const trackLoopbackErrorEvent = ( action, processType ) => {
|
||||
const properties = {
|
||||
Trigger: 'loopback_error',
|
||||
'Modal Action': action,
|
||||
Troubleshoot: isTroubleshootClicked ? 'Yes' : 'No',
|
||||
};
|
||||
|
||||
if ( 'scan' === processType ) {
|
||||
this.trackScanInterruptedEvent( properties );
|
||||
} else {
|
||||
this.trackBulkSmushInterruptedEvent( properties );
|
||||
}
|
||||
};
|
||||
|
||||
const closeButtons = loopbackErrorModal.querySelectorAll( '[data-modal-close]' );
|
||||
closeButtons.forEach( ( closeButton ) => {
|
||||
closeButton.addEventListener( 'click', ( e ) => {
|
||||
const action = e.target.dataset?.action || 'Close';
|
||||
const processType = loopbackErrorModal.dataset?.processType || 'scan';
|
||||
trackLoopbackErrorEvent( action, processType );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
registerTroubleshootClickEvent() {
|
||||
const troubleshootLinks = document.querySelectorAll( 'a[href*="#troubleshooting-guide"]' );
|
||||
if ( ! troubleshootLinks ) {
|
||||
return;
|
||||
}
|
||||
|
||||
troubleshootLinks.forEach( ( troubleshootLink ) => {
|
||||
troubleshootLink.addEventListener( 'click', () => {
|
||||
this.troubleshootClicked = true;
|
||||
}, { once: true } );
|
||||
} );
|
||||
}
|
||||
|
||||
maybeTrackMissedEventsOnLoad() {
|
||||
window.addEventListener( 'load', () => {
|
||||
const missedEvents = this.getMissedEvents();
|
||||
if ( 0 === missedEvents.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearMissedEvents();
|
||||
|
||||
missedEvents.forEach( ( missedEvent ) => {
|
||||
tracker.track( missedEvent.event, missedEvent.properties );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
registerBulkSmushResumeClickEvent() {
|
||||
const resumeBulkSmushButton = document.querySelector( '.wp-smush-resume-bulk-smush' );
|
||||
if ( ! resumeBulkSmushButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
resumeBulkSmushButton.addEventListener( 'click', () => {
|
||||
this.resumeBulkSmushCount += 1;
|
||||
} );
|
||||
}
|
||||
|
||||
getCurrentPageSlug() {
|
||||
const searchParams = new URLSearchParams( document.location.search );
|
||||
const pageSlug = searchParams.get( 'page' );
|
||||
return 'smush' === pageSlug ? 'dashboard' : pageSlug.replace( 'smush-', '' );
|
||||
}
|
||||
}
|
||||
|
||||
( new ProductAnalytics() ).init();
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
/* global ajaxurl */
|
||||
/* global wp_smush_msgs */
|
||||
/* global WP_Smush */
|
||||
/* global SUI */
|
||||
|
||||
( function( $ ) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Bulk compress page.
|
||||
*/
|
||||
$( 'form#smush-bulk-form' ).on( 'submit', function( e ) {
|
||||
e.preventDefault();
|
||||
$( '#save-settings-button' ).addClass( 'sui-button-onload' );
|
||||
saveSettings( $( this ).serialize(), 'bulk' );
|
||||
// runReCheck();
|
||||
} );
|
||||
|
||||
/**
|
||||
* Lazy load page.
|
||||
*/
|
||||
const lazyLoadForm = $( 'form#smush-lazy-preload-form' );
|
||||
lazyLoadForm.on( 'submit', function( e ) {
|
||||
e.preventDefault();
|
||||
const tabField = $( this ).find( '[name="tab"]' );
|
||||
const isLazyLoadPage = tabField.length && 'lazy_load' === tabField.val();
|
||||
|
||||
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
|
||||
saveSettings( $( this ).serialize(), isLazyLoadPage ? 'lazy-load' : 'preload' );
|
||||
} );
|
||||
|
||||
lazyLoadForm.on( 'keydown', function( e ) {
|
||||
if ( e.key === 'Enter' ) {
|
||||
lazyLoadForm.trigger( 'submit' );
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* CDN page.
|
||||
*/
|
||||
$( 'form#smush-cdn-form' ).on( 'submit', function( e ) {
|
||||
e.preventDefault();
|
||||
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
|
||||
saveSettings( $( this ).serialize(), 'cdn' );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Next-Gen page.
|
||||
*/
|
||||
$( 'form#smush-next-gen-form' ).on( 'submit', function( e ) {
|
||||
e.preventDefault();
|
||||
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
|
||||
saveSettings( $( this ).serialize(), 'next-gen' );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Integrations page.
|
||||
*/
|
||||
$( 'form#smush-integrations-form' ).on( 'submit', function( e ) {
|
||||
e.preventDefault();
|
||||
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
|
||||
saveSettings( $( this ).serialize(), 'integrations' );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Settings page.
|
||||
*/
|
||||
$( 'form#smush-settings-form' ).on( 'submit', function( e ) {
|
||||
e.preventDefault();
|
||||
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
|
||||
saveSettings( $( this ).serialize(), 'settings' );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Save settings.
|
||||
*
|
||||
* @param {string} settings JSON string of settings.
|
||||
* @param {string} page Settings page.
|
||||
*/
|
||||
function saveSettings( settings, page ) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open( 'POST', ajaxurl + '?action=smush_save_settings', true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
|
||||
xhr.onload = () => {
|
||||
$( '#save-settings-button' ).removeClass(
|
||||
'sui-button-onload-text sui-button-onload'
|
||||
);
|
||||
|
||||
if ( 200 === xhr.status ) {
|
||||
const res = JSON.parse( xhr.response );
|
||||
if ( 'undefined' !== typeof res.success && res.success ) {
|
||||
showSuccessNotice( wp_smush_msgs.settingsUpdated );
|
||||
triggerSavedSmushSettingsEvent( res.data );
|
||||
} else if ( res.data && res.data.message ) {
|
||||
WP_Smush.helpers.showErrorNotice( res.data.message );
|
||||
} else {
|
||||
WP_Smush.helpers.showErrorNotice( 'Request failed.' );
|
||||
}
|
||||
} else {
|
||||
WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send( 'page=' + page + '&' + settings + '&_ajax_nonce=' + wp_smush_msgs.nonce );
|
||||
}
|
||||
|
||||
function triggerSavedSmushSettingsEvent( status ) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent( 'onSavedSmushSettings', {
|
||||
detail: status
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show successful update notice.
|
||||
*
|
||||
* @param {string} msg Notice message.
|
||||
*/
|
||||
function showSuccessNotice( msg ) {
|
||||
const noticeMessage = `<p>${ msg }</p>`,
|
||||
noticeOptions = {
|
||||
type: 'success',
|
||||
icon: 'check',
|
||||
};
|
||||
|
||||
SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
|
||||
|
||||
const loadingButton = document.querySelector( '.sui-button-onload' );
|
||||
if ( loadingButton ) {
|
||||
loadingButton.classList.remove( 'sui-button-onload' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-check images from bulk smush and integrations pages.
|
||||
*/
|
||||
function runReCheck() {
|
||||
$( '#save-settings-button' ).addClass( 'sui-button-onload' );
|
||||
|
||||
const param = {
|
||||
action: 'scan_for_resmush',
|
||||
wp_smush_options_nonce: $( '#wp_smush_options_nonce' ).val(),
|
||||
type: 'media',
|
||||
};
|
||||
|
||||
// Send ajax, Update Settings, And Check For resmush.
|
||||
$.post( ajaxurl, $.param( param ) ).done( function() {
|
||||
$( '#save-settings-button' ).removeClass( 'sui-button-onload' );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse remove data change.
|
||||
*/
|
||||
$( 'input[name=keep_data]' ).on( 'change', function( e ) {
|
||||
const otherClass =
|
||||
'keep_data-true' === e.target.id
|
||||
? 'keep_data-false'
|
||||
: 'keep_data-true';
|
||||
e.target.parentNode.classList.add( 'active' );
|
||||
document
|
||||
.getElementById( otherClass )
|
||||
.parentNode.classList.remove( 'active' );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Handle highlighting notice display based on detection checkbox state.
|
||||
*/
|
||||
function handleHighlightingNotice( isAfterSave = false ) {
|
||||
const detectionEnabled = $( '#detection' ).is( ':checked' );
|
||||
const $notice = $( '.smush-highlighting-notice' );
|
||||
const $warning = $( '.smush-highlighting-warning' );
|
||||
|
||||
if ( ! detectionEnabled ) {
|
||||
$notice.hide();
|
||||
$warning.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isAfterSave ) {
|
||||
$notice.show();
|
||||
$warning.hide();
|
||||
} else {
|
||||
// Show warning only if notice isn't visible yet.
|
||||
const noticeVisible = $notice.is( ':visible' ) || $notice.css( 'display' ) !== 'none';
|
||||
if ( noticeVisible ) {
|
||||
$notice.show();
|
||||
$warning.hide();
|
||||
} else {
|
||||
$notice.hide();
|
||||
$warning.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle auto-detect checkbox toggle, to show/hide highlighting notice.
|
||||
*/
|
||||
$( 'input#detection' ).on( 'click', function () {
|
||||
handleHighlightingNotice( false );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Handle settings saved event.
|
||||
*/
|
||||
$( document ).on( 'onSavedSmushSettings', function () {
|
||||
handleHighlightingNotice( true );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Form notice via query var smush-notice handler.
|
||||
*/
|
||||
const formNoticeHandler = () => {
|
||||
if ( ! window.URL || ! window.URLSearchParams ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL( window.location );
|
||||
const noticeKey = url.searchParams.get( 'smush-notice' );
|
||||
if ( ! noticeKey ) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.dispatchEvent(
|
||||
new CustomEvent( `on-smush-${ noticeKey }-notice` )
|
||||
);
|
||||
|
||||
// Remove the smush-notice query parameter.
|
||||
url.searchParams.delete( 'smush-notice' );
|
||||
window.history.replaceState( {}, document.title, url.toString() );
|
||||
};
|
||||
|
||||
formNoticeHandler();
|
||||
|
||||
// Handle toggle fields.
|
||||
const toggleFieldVisibility = ( fieldVisibilitySettings, fieldWrapperClassName ) => {
|
||||
for ( const fieldName in fieldVisibilitySettings ) {
|
||||
const field = document.querySelector( `[name="${ fieldName }"]` );
|
||||
if ( ! field ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldWrapper = field.closest( `.${ fieldWrapperClassName }` );
|
||||
if ( ! fieldWrapper ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'show' === fieldVisibilitySettings[ fieldName ] ) {
|
||||
fieldWrapper.classList.remove( 'sui-hidden' );
|
||||
} else {
|
||||
fieldWrapper.classList.add( 'sui-hidden' );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toggleFieldsHandler = () => {
|
||||
const settingsForm = document.querySelector( '.wp-smush-settings-form' );
|
||||
if ( ! settingsForm ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldWrapperClassName = 'sui-box-settings-row';
|
||||
const conditionalFields = settingsForm.querySelectorAll( '[data-toggle-fields]' );
|
||||
if ( conditionalFields ) {
|
||||
conditionalFields.forEach( function( conditionalField ) {
|
||||
const fieldVisibilitySettings = JSON.parse( conditionalField.dataset.toggleFields );
|
||||
if ( ! fieldVisibilitySettings && 'object' !== typeof fieldVisibilitySettings ) {
|
||||
return;
|
||||
}
|
||||
|
||||
conditionalField.addEventListener( 'change', function() {
|
||||
if ( ! this.checked ) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleFieldVisibility( fieldVisibilitySettings, fieldWrapperClassName );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
if ( window.SUI?.tabs ) {
|
||||
window.SUI.tabs(
|
||||
{
|
||||
callback( tab, panel ) {
|
||||
const conditionalField = tab.next( '[data-toggle-fields]' );
|
||||
if ( conditionalField.length ) {
|
||||
const fieldVisibilitySettings = conditionalField.data( 'toggle-fields' );
|
||||
if ( fieldVisibilitySettings && 'object' === typeof fieldVisibilitySettings ) {
|
||||
toggleFieldVisibility( fieldVisibilitySettings, fieldWrapperClassName );
|
||||
}
|
||||
}
|
||||
|
||||
const childFields = panel.find( '[data-toggle-fields]' );
|
||||
if ( childFields.length ) {
|
||||
childFields.each( function() {
|
||||
if ( $( this ).is( ':checked' ) ) {
|
||||
const fieldVisibilitySettings = $( this ).data( 'toggle-fields' );
|
||||
if ( fieldVisibilitySettings && 'object' === typeof fieldVisibilitySettings ) {
|
||||
toggleFieldVisibility( fieldVisibilitySettings, fieldWrapperClassName );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
toggleFieldsHandler();
|
||||
}( jQuery ) );
|
||||
+1241
File diff suppressed because it is too large
Load Diff
+133
@@ -0,0 +1,133 @@
|
||||
/* global WP_Smush */
|
||||
/* global ajaxurl */
|
||||
|
||||
/**
|
||||
* WebP functionality.
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
import Fetcher from '../utils/fetcher';
|
||||
import NextGen from './next-gen';
|
||||
|
||||
class WebP extends NextGen {
|
||||
constructor() {
|
||||
super( 'webp' );
|
||||
|
||||
this.recheckStatusButton = document.getElementById( 'smush-webp-recheck' );
|
||||
this.recheckStatusLink = document.getElementById( 'smush-webp-recheck-link' );
|
||||
this.showWizardButton = document.getElementById( 'smush-webp-toggle-wizard' );
|
||||
this.switchWebpMethod = document.getElementById( 'smush-switch-webp-method' );
|
||||
|
||||
this.webpInit();
|
||||
}
|
||||
|
||||
webpInit() {
|
||||
/**
|
||||
* Handle "RE-CHECK STATUS' button click on WebP page.
|
||||
*/
|
||||
if ( this.recheckStatusButton ) {
|
||||
this.recheckStatusButton.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
this.recheckStatus();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "RE-CHECK STATUS' link click on WebP page.
|
||||
*/
|
||||
if ( this.recheckStatusLink ) {
|
||||
this.recheckStatusLink.addEventListener( 'click', ( e ) => {
|
||||
e.preventDefault();
|
||||
this.recheckStatus();
|
||||
} );
|
||||
}
|
||||
|
||||
if ( this.showWizardButton ) {
|
||||
this.showWizardButton.addEventListener(
|
||||
'click',
|
||||
this.toggleWizard
|
||||
);
|
||||
}
|
||||
|
||||
if ( this.switchWebpMethod ) {
|
||||
this.switchWebpMethod.addEventListener(
|
||||
'click',
|
||||
( e ) => {
|
||||
e.preventDefault();
|
||||
e.target.classList.add( 'wp-smush-link-in-progress' );
|
||||
this.switchMethod( this.switchWebpMethod.dataset.method );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
switchMethod( newMethod ) {
|
||||
Fetcher.webp.switchMethod( newMethod ).then( ( res ) => {
|
||||
if ( ! res?.success ) {
|
||||
WP_Smush.helpers.showNotice( res );
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* re-check server configuration for WebP.
|
||||
*/
|
||||
recheckStatus() {
|
||||
this.recheckStatusButton.classList.add( 'sui-button-onload' );
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( 'POST', ajaxurl + '?action=smush_webp_get_status', true );
|
||||
xhr.setRequestHeader(
|
||||
'Content-type',
|
||||
'application/x-www-form-urlencoded'
|
||||
);
|
||||
xhr.onload = () => {
|
||||
this.recheckStatusButton.classList.remove( 'sui-button-onload' );
|
||||
let message = false;
|
||||
const res = JSON.parse( xhr.response );
|
||||
if ( 200 === xhr.status ) {
|
||||
const isConfigured = res.success ? '1' : '0';
|
||||
if (
|
||||
isConfigured !==
|
||||
this.recheckStatusButton.dataset.isConfigured
|
||||
) {
|
||||
// Reload the page when the configuration status changed.
|
||||
location.reload();
|
||||
}
|
||||
} else {
|
||||
message = window.wp_smush_msgs.generic_ajax_error;
|
||||
}
|
||||
|
||||
if ( res && res.data ) {
|
||||
message = res.data;
|
||||
}
|
||||
|
||||
if ( message ) {
|
||||
this.showNotice( message );
|
||||
}
|
||||
};
|
||||
xhr.send( '_ajax_nonce=' + window.wp_smush_msgs.webp_nonce );
|
||||
}
|
||||
|
||||
toggleWizard( e ) {
|
||||
e.currentTarget.classList.add( 'sui-button-onload' );
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
'GET',
|
||||
ajaxurl +
|
||||
'?action=smush_toggle_webp_wizard&_ajax_nonce=' +
|
||||
window.wp_smush_msgs.webp_nonce,
|
||||
true
|
||||
);
|
||||
xhr.onload = () => location.href = window.wp_smush_msgs.nextGenURL;
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
|
||||
( function() {
|
||||
'use strict';
|
||||
WP_Smush.WebP = new WebP();
|
||||
}() );
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/* global ajaxurl */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import assign from 'lodash/assign';
|
||||
|
||||
/**
|
||||
* Wrapper function for ajax calls to WordPress.
|
||||
*
|
||||
* @since 3.12.0
|
||||
*/
|
||||
function SmushFetcher() {
|
||||
/**
|
||||
* Request ajax with a promise.
|
||||
* Use FormData Object as data if you need to upload file
|
||||
*
|
||||
* @param {string} action
|
||||
* @param {Object|FormData} data
|
||||
* @param {string} method
|
||||
* @return {Promise<any>} Request results.
|
||||
*/
|
||||
function request(action, data = {}, method = 'POST') {
|
||||
const args = {
|
||||
url: ajaxurl,
|
||||
method,
|
||||
cache: false
|
||||
};
|
||||
|
||||
if (data instanceof FormData) {
|
||||
data.append('action', action);
|
||||
data.append('_ajax_nonce', window.wp_smush_msgs.nonce);
|
||||
args.contentType = false;
|
||||
args.processData = false;
|
||||
} else {
|
||||
data._ajax_nonce = data._ajax_nonce || window.smush_global.nonce || window.wp_smush_msgs.nonce;
|
||||
data.action = action;
|
||||
}
|
||||
args.data = data;
|
||||
return new Promise((resolve, reject) => {
|
||||
jQuery.ajax(args).done(resolve).fail(reject);
|
||||
}).then((response) => {
|
||||
if (typeof response !== 'object') {
|
||||
response = JSON.parse(response);
|
||||
}
|
||||
return response;
|
||||
}).catch((error) => {
|
||||
console.error('Error:', error);
|
||||
return error;
|
||||
});
|
||||
}
|
||||
|
||||
const methods = {
|
||||
/**
|
||||
* Manage ajax for background.
|
||||
*/
|
||||
background: {
|
||||
/**
|
||||
* Start background process.
|
||||
*/
|
||||
start: () => {
|
||||
return request('bulk_smush_start');
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel background process.
|
||||
*/
|
||||
cancel: () => {
|
||||
return request('bulk_smush_cancel');
|
||||
},
|
||||
|
||||
/**
|
||||
* Initial State - Get stats on the first time.
|
||||
*/
|
||||
initState: () => {
|
||||
return request('bulk_smush_get_status');
|
||||
},
|
||||
|
||||
/**
|
||||
* Get stats.
|
||||
*/
|
||||
getStatus: () => {
|
||||
return request('bulk_smush_get_status');
|
||||
},
|
||||
|
||||
getStats: () => {
|
||||
return request('bulk_smush_get_global_stats');
|
||||
},
|
||||
|
||||
backgroundHealthyCheck: () => {
|
||||
return request('smush_start_background_pre_flight_check');
|
||||
},
|
||||
|
||||
backgroundHealthyStatus: () => {
|
||||
return request('smush_get_background_pre_flight_status');
|
||||
}
|
||||
},
|
||||
smush: {
|
||||
/**
|
||||
* Sync stats.
|
||||
*/
|
||||
syncStats: ( data ) => {
|
||||
data = data || {};
|
||||
return request('get_stats', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Ignore All.
|
||||
*/
|
||||
ignoreAll: ( type ) => {
|
||||
return request('wp_smush_ignore_all_failed_items', {
|
||||
type: type,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Manage ajax for other requests
|
||||
*/
|
||||
common: {
|
||||
/**
|
||||
* Dismiss Notice.
|
||||
*
|
||||
* @param {string} dismissId Notification id.
|
||||
*/
|
||||
dismissNotice: (dismissId) => {
|
||||
return request('smush_dismiss_notice', {
|
||||
key: dismissId
|
||||
});
|
||||
},
|
||||
|
||||
remindReviewPrompt: () => {
|
||||
return request( 'wp_smush_review_prompts_remind_later' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the new features modal.
|
||||
*
|
||||
* @param {string} modalID Notification id.
|
||||
*/
|
||||
hideModal: (modalID) => request('hide_modal', {
|
||||
modal_id: modalID,
|
||||
}),
|
||||
|
||||
track: ( event, properties ) => request('smush_analytics_track_event', {
|
||||
event,
|
||||
properties
|
||||
}),
|
||||
|
||||
/**
|
||||
* Custom request.
|
||||
*
|
||||
* @param {Object} data
|
||||
*/
|
||||
request: (data) => data.action && request(data.action, data),
|
||||
},
|
||||
|
||||
scanMediaLibrary: {
|
||||
start: ( optimize_on_scan_completed = false ) => {
|
||||
optimize_on_scan_completed = optimize_on_scan_completed ? 1 : 0;
|
||||
const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
|
||||
return request( 'wp_smush_start_background_scan', {
|
||||
optimize_on_scan_completed,
|
||||
_ajax_nonce,
|
||||
} );
|
||||
},
|
||||
|
||||
cancel: () => {
|
||||
const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
|
||||
return request( 'wp_smush_cancel_background_scan', {
|
||||
_ajax_nonce,
|
||||
} );
|
||||
},
|
||||
|
||||
getScanStatus: () => {
|
||||
const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
|
||||
return request( 'wp_smush_get_background_scan_status', {
|
||||
_ajax_nonce,
|
||||
} );
|
||||
},
|
||||
},
|
||||
|
||||
webp: {
|
||||
switchMethod: ( method ) => {
|
||||
return request( 'webp_switch_method', { method } );
|
||||
},
|
||||
},
|
||||
|
||||
settings: {
|
||||
disconnectSite: () => {
|
||||
return request( 'wp_smush_disconnect_site' );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assign(this, methods);
|
||||
}
|
||||
|
||||
const SmushAjax = new SmushFetcher();
|
||||
export default SmushAjax;
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import Fetcher from './fetcher';
|
||||
|
||||
class Tracker {
|
||||
/* @private */
|
||||
#doingEvents = new Set();
|
||||
#allowToTrack;
|
||||
|
||||
track( event, properties = {} ) {
|
||||
if ( ! this.allowToTrack() || this.inProgressEvent( event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setInProgressEvent( event );
|
||||
|
||||
return Fetcher.common.track( event, properties ).then( ( res ) => {
|
||||
setTimeout( () => {
|
||||
this.restoreInProgressEvent( event );
|
||||
}, 1000 );
|
||||
|
||||
return res;
|
||||
} );
|
||||
}
|
||||
|
||||
inProgressEvent( event ) {
|
||||
return this.#doingEvents.has( event );
|
||||
}
|
||||
|
||||
setInProgressEvent( event ) {
|
||||
this.#doingEvents.add( event );
|
||||
}
|
||||
|
||||
restoreInProgressEvent( event ) {
|
||||
this.#doingEvents.delete( event );
|
||||
}
|
||||
|
||||
allowToTrack() {
|
||||
return this.#allowToTrack || !! ( window.wp_smush_mixpanel?.opt_in );
|
||||
}
|
||||
|
||||
setAllowToTrack( allowToTrack ) {
|
||||
this.#allowToTrack = allowToTrack;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
const tracker = new Tracker();
|
||||
|
||||
export default tracker;
|
||||
Reference in New Issue
Block a user