Current File : /home/honehdyv/readbtooom.com/wp-content/plugins/autodescription/lib/js/tsf.js |
/**
* This file holds The SEO Framework plugin's JS code.
* Serve JavaScript as an addition, not as a means.
*
* @author Sybre Waaijer <https://cyberwire.nl/>
* @link https://wordpress.org/plugins/autodescription/
*/
/**
* The SEO Framework plugin
* Copyright (C) 2015 - 2023 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
/**
* The more you know 🌈⭐ We use this babel configuration:
*
* "browserslist": "> 1.25%, not dead",
* "devDependencies": {
* "@babel/core": "^7.3.4",
* "@babel/preset-env": "^7.3.4",
* "babel-minify": "^0.5.0",
* "browserslist": "^4.0.0"
* }
*/
/**
* Holds The SEO Framework values in an object to avoid polluting global namespace.
*
* @since 2.2.4
* @since 4.0.0 Thinned code, spread over more files.
*
* @constructor
* @param {!jQuery} $ jQuery object.
*/
window.tsf = function( $ ) {
/**
* Data property injected by WordPress l10n handler.
*
* @since 4.0.0
* @access public
* @type {(Object<string, *>)|boolean|null} l10n Localized strings
*/
const l10n = 'undefined' !== typeof tsfL10n && tsfL10n;
/**
* Mimics PHP's strip_tags in a rudimentarily form, without allowed tags.
*
* PHP's version checks every single character to comply with the allowed tags,
* whereas we simply use regex. This acts as a carbon-copy, regardless.
*
* @since 3.1.0
* @access public
*
* @function
* @param {String} str The text to strip tags from.
* @return {String} The stripped tags.
*/
const stripTags = str => str.length && str.replace( /(<([^>]+)?>?)/ig, '' ) || '';
let _decodeEntitiesDOMParser = void 0,
_decodeEntitiesMap = {
'<': '<',
'>': '>',
"\\": '\',
};
/**
* Decodes string entities securely.
*
* Uses a fallback when the browser doesn't support DOMParser.
* This fallback sends out exactly the same output.
*
* The rendering of this function is considered secure against XSS attacks.
* However, you must consider the output as insecure HTML, and may only append via innerText.
*
* @since 4.0.0
* @access public
* @see tsf.escapeString;
*
* @credit <https://stackoverflow.com/questions/1912501/unescape-html-entities-in-javascript/34064434#34064434>
* Modified to allow <, >, and \ entities, and cached the parser.
*
* @param {String} str The text to decode.
* @return {String} The decoded text.
*/
const decodeEntities = str => {
if ( ! str?.length ) return '';
_decodeEntitiesDOMParser ||= new DOMParser();
return _decodeEntitiesDOMParser.parseFromString(
// Prevent "tags" from being stripped. When not string, return ''.
str.replace?.( /[<>\\]/g, m => _decodeEntitiesMap[ m ] ) || '',
'text/html'
).documentElement.textContent;
}
/**
* Escapes input string.
*
* @since 3.0.1
* @since 3.1.2 Now escapes backslashes correctly.
* @since 4.0.0 1. Now escapes all backslashes, instead of only double.
* 2. Now escapes forward slashes:
* Although unlikely, some HTML parsers may omit the closing " of an attribute,
* which may cause the slash to close the HTML tag.
* @access public
*
* @function
* @param {string} str
* @return {string}
*/
const escapeString = str => {
if ( ! str?.length ) return '';
let map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
"\\": '\',
"/": '/',
};
// When not string, return ''
return str.replace?.( /[&<>"'\\\/]/g, m => map[ m ] ) || '';
}
/**
* Converts ampersands HTML entity (`&`) to text (`&`).
*
* @since 4.0.0
* @access public
* @todo deprecate, unused internally. Should've also been named ampEntitytoHTML.
*
* @function
* @param {string} str
* @return {string}
*/
const ampHTMLtoText = str => str.replace( /&|�{0,3}26;|&/gi, '&' );
/**
* Removes duplicated spaces from strings.
*
* @since 3.1.0
* @access public
*
* @function
* @param {string} str
* @return {string}
*/
const sDoubleSpace = str => str.replace( /\s{2,}/g, ' ' );
/**
* Removes line feeds from strings.
*
* @since 4.2.0
* @access public
*
* @function
* @param {string} str
* @return {string}
*/
const sSingleLine = str => str.replace( /[\x0A\x0B\x0C\x0D]/g, ' ' );
/**
* Removes line feeds from strings.
*
* @since 4.2.0
* @access public
*
* @function
* @param {string} str
* @return {string}
*/
const sTabs = str => str.replace( /\x09/g, ' ' );
/**
* Gets string length.
*
* @since 3.0.0
* @access public
*
* @function
* @param {string} str
* @return {number}
*/
const getStringLength = str => {
let e,
length = 0;
if ( str.length ) {
e = document.createElement( 'span' );
e.innerHTML = escapeString( str ).trim();
// Trimming can lead to empty child nodes. Test for undefined.
length = e.childNodes?.[0].nodeValue.length || 0;
}
return +length;
}
/**
* Sets select option by value.
* First tests the value attribute, then the content.
*
* @since 4.0.0
* @access public
*
* @function
* @param {HTMLSelectElement} element The element to select an item in.
* @param {string} value The value of the element ot set the index to.
*/
const selectByValue = ( element, value ) => {
if ( ! element instanceof HTMLSelectElement ) return;
let _newIndex = void 0;
// Try by value.
for ( let i = 0; i < element.options.length; ++i ) {
if ( value == element.options[ i ].value ) {
_newIndex = i;
break;
}
}
if ( void 0 === _newIndex ) {
// Try by content.
for ( let i = 0; i < element.options.length; ++i ) {
if ( value == element.options[ i ].innerHTML ) {
_newIndex = i;
break;
}
}
}
if ( void 0 !== _newIndex ) {
element.selectedIndex = _newIndex;
}
}
/**
* Tries to convert JSON response to values if not already set.
*
* @since 3.1.0
* @access public
*
* @function
* @param {(object|string|undefined)} response
* @return {(object|undefined)}
*/
const convertJSONResponse = response => {
let testJSON = response && response.json || void 0,
isJSON = 1 === testJSON;
if ( ! isJSON ) {
let _response = response;
try {
response = JSON.parse( response );
isJSON = true;
} catch ( e ) {
isJSON = false;
}
if ( ! isJSON ) {
// Reset response.
response = _response;
}
}
return response;
}
/**
* Visualizes AJAX loading time through target class change.
*
* @since 2.7.0
* @access public
*
* @function
* @param {String|Element|jQuery.Element} target
*/
const setAjaxLoader = target => {
$( target ).toggleClass( 'tsf-loading' );
}
/**
* Adjusts class loaders on Ajax response.
*
* @since 2.7.0
* @access public
*
* @function
* @param {String|Element|jQuery.Element} target
* @param {Boolean} success
*/
const unsetAjaxLoader = ( target, success ) => {
const newclass = 'tsf-success',
fadeTime = 2500;
if ( ! success ) {
newclass = 'tsf-error';
fadeTime = 5000;
}
$( target ).removeClass( 'tsf-loading' ).addClass( newclass ).fadeOut( fadeTime );
}
/**
* Cleans and resets Ajax wrapper class and contents to default.
* Also stops any animation and resets fadeout to beginning.
*
* @since 2.7.0
* @since 4.1.0 Now ends animations instead of halting. Instantly shows the element again.
* @access public
*
* @function
* @param {String|Element|jQuery.Element} target
*/
const resetAjaxLoader = target => {
$( target ).stop( false, true ).empty().prop( 'class', 'tsf-ajax' ).show();
}
/**
* Outputs deprecation warning to console.
*
* @since 4.1.0
* @access private
*
* @function
* @param {string} target
* @param {string} version
* @param {string} replacement
*/
const deprecatedFunc = ( name, version, replacement ) => {
version = version ? ` since The SEO Framework ${version}` : '';
replacement = replacement ? ` Use ${replacement} instead.` : '';
console.warn( `[DEPRECATED]: ${name} is deprecated${version}.${replacement}` );
}
/**
* Sets postbox toggle handlers.
* TODO move to Settings.js and Post.js respectively?
*
* TODO also check for hide-postbox-tog... it prevents the user from saving the page.
*
* @since 4.0.0
* @since 4.1.0 No longer causes an infinite loop (call stack size excession).
* @access private
*
* @function
*/
const _initPostboxToggle = () => {
// Get TSF postboxes. Move this inside of the event for the "dynamic web"?
let $postboxes = $( '.postbox[id^="autodescription-"], .postbox#tsf-inpost-box' );
/**
* HACK: Reopens a box if it contains invalid input values, and notifies the users thereof.
* WordPress should implement this in a non-hacky way, so to give us more freedom.
*
* Alternatively, we could validate the input and reopen the boxes when the user hits "save".
* I do prefer the direct feedback though.
*
* Note that this event might get deprecated!
*/
$( document ).on( 'postbox-toggled', ( event, $postbox ) => {
if ( ! $postbox || ! $postboxes.is( $postbox ) ) return;
// WordPress bug--they send an array but should've sent it within one.
// Let's assume they might fix it by converting it to jQuery.
$postbox = $( $postbox );
let $input = $postbox.find( 'input:invalid, select:invalid, textarea:invalid' );
if ( ! $input.length ) return;
// Defer from event.
setTimeout( () => {
if ( $postbox.is( ':hidden' ) ) {
let id = $postbox.attr( 'id' );
// Unhide the postbox. Then, loop back to the other parts.
$( `#${id}-hide` ).trigger( 'click.postboxes' );
} else {
if ( $postbox.hasClass( 'closed' ) ) {
// Reopen self. Loops back to this function.
$postbox.find( '.hndle, .handlediv' ).first().trigger( 'click.postboxes' );
} else {
// Phase 2, this runs after looping back.
let firstInput = $input.get( 0 );
if ( $( firstInput ).is( ':visible' ) ) {
firstInput.reportValidity();
}
}
}
} );
} );
}
/**
* Prepares notice dismissal listeners.
*
* @since 4.1.2
* @access private
*
* @function
*/
const _initNotices = () => {
/**
* Dismissible notices that use notice wrapper class .tsf-notice.
*
* @since 2.6.0
* @since 2.9.3 Now correctly removes the node from DOM.
* @since 4.1.0 1. Now is more in line with how WordPress dismisses notices.
* 2. Now also handles dismissible persistent notices.
* @since 4.1.2 Moved inside other method.
*
* @function
* @param {Event} event
*/
const dismissNotice = event => {
const $notice = $( event.target ).closest( '.tsf-notice' ).first(),
key = event.target.dataset && event.target.dataset.key || void 0,
nonce = event.target.dataset && event.target.dataset.nonce || void 0;
$notice.fadeTo( 100, 0, () => {
$notice.slideUp( 100, () => {
$notice.remove();
} );
} );
if ( key && nonce ) {
// The notice is removed regardless of this being completed.
// Do not inform the user of its completion--it adds a lot to the annoyance.
// Instead, rely on keeping the 'count' low!
wp.ajax.post(
'tsf_dismiss_notice',
{
tsf_dismiss_key: key,
tsf_dismiss_nonce: nonce,
}
);
}
}
const reset = () => {
// Enable dismissal of PHP-inserted notices.
document.querySelectorAll( '.tsf-dismiss' ).forEach(
el => {
el.addEventListener( 'click', dismissNotice )
}
);
}
/**
* @access private Use triggerNoticeReset() instead.
*/
document.body.addEventListener( 'tsf-reset-notice-listeners', reset );
reset();
}
let _dispatchEvents = new Set(),
_loadedDispatchEvent = false;
/**
* Offsets callback to interactive event.
*
* @since 4.2.1
* @access public
*
* @function
* @param {Element} element The element to dispatch the event upon.
* @param {string} eventName The event name to trigger. Mustn't be custom.
*/
const dispatchAtInteractive = ( element, eventName ) => {
_dispatchEvents.add( [ element, eventName ] );
if ( ! _loadedDispatchEvent ) {
document.body.addEventListener( 'tsf-interactive', _loopDispatchAtInteractive );
_loadedDispatchEvent = true;
}
}
/**
* Runs callbacks at interactive event.
*
* @since 4.2.0
* @access private
*
* @function
*/
const _loopDispatchAtInteractive = () => {
_dispatchEvents.forEach( ( [ element, eventName ] ) => {
element.dispatchEvent( new Event( eventName ) );
} );
}
let _debounceNoticeReset;
/**
* Invokes notice dismissal listener reset.
*
* @since 4.1.2
* @access public
*
* @function
*/
const triggerNoticeReset = () => {
clearTimeout( _debounceNoticeReset );
_debounceNoticeReset = setTimeout(
() => document.body.dispatchEvent( new CustomEvent( 'tsf-reset-notice-listeners' ) ),
100 // Magic number. Low enough not to cause ignored clicks, high enough not to cause lag.
);
}
let _debounceResize,
_debounceResizeTrigger,
_throttleResize = false;
const _throttleResizeDebounceDelay = 50;
/**
* Dispatches tsf-resize event on window.
*
* It fires immediately, after which it's debounced indefinitely until 100ms passed.
* Once debounce is passed, another immediate trigger can happen again.
*
* @since 4.2.0
* @access private
*
* @function
*/
const _triggerResize = () => {
clearTimeout( _debounceResize );
_debounceResize = setTimeout( () => { _throttleResize = false }, _throttleResizeDebounceDelay );
if ( _throttleResize ) {
clearTimeout( _debounceResizeTrigger );
_debounceResizeTrigger = setTimeout( _triggerResize, _throttleResizeDebounceDelay );
} else {
_throttleResize = true;
dispatchEvent( new CustomEvent( 'tsf-resize' ) );
}
}
let isInteractive = false;
/**
* Dispatches tsf-interactive event.
*
* This fires as soon as all TSF script are done loading. A few more may load here that rely on user interaction.
* Use case: User is expected to interact confidently with the page. (This obviously isn't true, since WP is slow, but one day...)
*
* Feel free to asynchronously do things at this point.
*
* Example: jQuery( document.body ).on( 'tsf-interactive', myFunc );
* Or: document.body.addEventListener( 'tsf-interactive', myFunc );
*
* @since 4.1.1
* @access private
*
* @function
*/
const _triggerInteractive = () => {
if ( ! isInteractive ) {
isInteractive = true;
document.body.dispatchEvent( new CustomEvent( 'tsf-interactive' ) );
}
}
/**
* Dispatches tsf-ready event.
*
* This fires as soon as all TSF scripts have registered their interactions.
* Use case: User may still see elements painting.
*
* You should not work asynchronously here.
* ...yet we do by triggering "enqueueTriggerInput" events. We need to fix that.
*
* Example: jQuery( document.body ).on( 'tsf-ready', myFunc );
* Or: document.body.addEventListener( 'tsf-ready', myFunc );
*
* @since 2.9.0
* @access private
*
* @function
*/
const _triggerReady = () => {
document.body.dispatchEvent( new CustomEvent( 'tsf-ready' ) );
}
/**
* Dispatches 'tsf-onload' event.
*
* This fires as soon as all TSF scripts are loaded.
* Use case: User still sees a white screen, window has yet to be painted.
*
* You should not work asynchronously here.
*
* Example: jQuery( document.body ).on( 'tsf-onload, myFunc );
* Or: document.body.addEventListener( 'tsf-onload', myFunc );
*
* @since 3.1.0
* @access private
*
* @function
*/
const _triggerOnLoad = () => {
document.body.dispatchEvent( new CustomEvent( 'tsf-onload' ) );
}
let _isReady = false;
/**
* Runs document-on-ready actions.
*
* @since 3.0.0
* @access private
*
* @function
*/
const _doReady = () => {
if ( _isReady ) return;
document.removeEventListener( "DOMContentLoaded", _doReady );
document.removeEventListener( "load", _doReady );
// Triggers tsf-onload event.
_triggerOnLoad();
// Sets postbox toggles on load.
_initPostboxToggle();
// Initializes notices
_initNotices();
// Trigger tsf-ready event.
_triggerReady();
_isReady = true;
// Trigger tsf-interactive event. 'load' might be too late 'cause images are loading (slow 3G...)
document.addEventListener( 'load', _triggerInteractive );
setTimeout( _triggerInteractive, 100 ); // Magic number. Low enough to be negligible. High enough to let other scripts finish.
}
return Object.assign( {
/**
* Initialises all aspects of the scripts.
* You shouldn't call this.
*
* @since 4.0.0
* @access protected
*
* @function
*/
load: () => {
/**
* From: https://developer.akamai.com/blog/2017/12/04/beware-performancetimingdominteractive
* "Overall, I found that pages with external (not in the HTML) CSS, JavaScript, or fonts
* could lead to inaccurate estimations of domInteractive"
*/
// document.addEventListener( 'readystatechange', () => {
// // Interactive means that the document was fully read. However, we cannot reliably determine if all dependencies have been loaded?
// [ 'interactive', 'complete' ].includes( document.readyState ) && _doReady();
// } );
/**
* The code below isn't affected by the above mentioned issues; albeit not as smoothly executed as we'd like...
* such as any page on theseoframework.com; which benefit from considering load order & inline scripts, making for seamless rendering.
*
* WordPress admin always forces us to load JS assets last--at least, when we do things by their book. We should
* honor this, at the expense of extra layout shifts and delayed rendering of critical markup.
*
* @source jQuery 3.5.1
*/
if ( document.readyState === "complete" ||
( document.readyState !== "loading" && ! document.documentElement.doScroll ) ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready.
setTimeout( _doReady() );
} else {
document.addEventListener( "DOMContentLoaded", _doReady );
document.addEventListener( "load", _doReady );
}
// Trigger tsf-resize event.
window.addEventListener( 'resize', _triggerResize );
}
}, {
stripTags,
decodeEntities,
escapeString,
ampHTMLtoText,
sDoubleSpace,
sSingleLine,
sTabs,
getStringLength,
selectByValue,
convertJSONResponse,
setAjaxLoader,
unsetAjaxLoader,
resetAjaxLoader,
deprecatedFunc,
triggerNoticeReset,
dispatchAtInteractive,
}, {
l10n
} );
}( jQuery );
window.tsf.load();