Current File : /home/honehdyv/readbtooom.com/wp-content/plugins/autodescription/lib/js/description.js |
/**
* This file holds The SEO Framework plugin's JS code for TSF description fields.
* Serve JavaScript as an addition, not as an ends or means.
*
* @author Sybre Waaijer <https://cyberwire.nl/>
* @link <https://wordpress.org/plugins/autodescription/>
*/
/**
* The SEO Framework plugin
* Copyright (C) 2019 - 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';
/**
* Holds tsfDescription values in an object to avoid polluting global namespace.
*
* This is a self-constructed function assigned as an object.
*
* @since 4.0.0
*
* @constructor
*/
window.tsfDescription = function() {
/**
* @since 4.1.0
* @type {Map<string,Element>} The input element instances.
*/
const descriptionInputInstances = new Map();
/**
* @since 4.1.0
* @access private
* @type {(Object<string,Object<string,*>)} the query state.
*/
const states = {};
/**
* Sets input element for all listeners. Must be called prior interacting with this object.
* Resets the state for the input ID.
*
* @since 4.0.0
* @since 4.1.0 Now creates an instance in a map this object, and returns it.
* @access public
*
* @param {Element} element
* @return {Element}
*/
const setInputElement = element => {
descriptionInputInstances.set( element.id, element );
states[ element.id ] = {
allowReferenceChange: true,
defaultDescription: '',
useDefaultDescription: true,
};
_loadDescriptionActions( element );
return getInputElement( element.id );
}
/**
* Gets input element, if exists.
*
* @since 4.1.0
* @access public
*
* @param {string} id The element ID.
* @return {Element}
*/
const getInputElement = id => descriptionInputInstances.get( id );
/**
* Returns state of ID.
*
* @since 4.1.0
* @access public
*
* @param {string} id The input element ID.
* @param {(string|undefined)} part The part to return. Leave empty to return the whole state.
* @return {(Object<string, *>)|*|null}
*/
const getStateOf = ( id, part ) => part ? states[ id ]?.[ part ] : states[ id ];
/**
* Updates state of ID.
*
* There's no need to escape the input, it may be double-escaped if you do so.
*
* @since 4.1.0
* @since 4.1.2 Added part `useDefaultDescription`.
* @since 4.2.0 Now remains intert on a non-change.
* @access public
*
* @param {string} id The input element ID.
* @param {string} part The state index to change.
* @param {*} value The value to set the state to.
*/
const updateStateOf = ( id, part, value ) => {
if ( states[ id ][ part ] === value ) return;
states[ id ][ part ] = value;
switch ( part ) {
case 'allowReferenceChange':
case 'defaultDescription':
case 'useDefaultDescription':
default:
enqueueTriggerInput( id );
break;
}
}
/**
* Updates state of all elements.
*
* There's no need to escape the input, it may be double-escaped if you do so.
*
* @since 4.1.0
* @since 4.2.0 Added a 3rd parameter, allowing you to exclude updates for certain elements.
* @access public
*
* @param {string} part The state index to change.
* @param {*} value The value to set the state to.
* @param {string|string[]} except The input element IDs to exclude from updates.
*/
const updateStateAll = ( part, value, except ) => {
except = Array.isArray( except ) ? except : [ except ];
descriptionInputInstances.forEach( element => {
if ( except.includes( element.id ) ) return;
updateStateOf( element.id, part, value );
} );
}
/**
* Returns description references of ID.
*
* @since 4.1.0
* @access public
*
* @param {string} id The input element ID.
* @return {HTMLElement[]}
*/
const _getDescriptionReferences = id => {
let references = [ document.getElementById( `tsf-description-reference_${id}` ) ];
if ( getStateOf( id, 'hasLegacy' ) ) {
let legacy = document.getElementById( 'tsf-description-reference' );
legacy && references.unshift( legacy );
}
return references;
}
/**
* Updates the description reference.
*
* Used by the character counters, pixel counters, and social meta inputs.
*
* @since 4.0.0
* @since 4.1.0 Now supports multiple instances.
* @since 4.1.2 Now listens to `useDefaultDescription` when reference isn't locked.
* @access private
*
* @function
* @param {Event} event
*/
const _setReferenceDescription = event => {
const references = _getDescriptionReferences( event.target.id );
if ( ! references[0] ) return;
const allowReferenceChange = getStateOf( event.target.id, 'allowReferenceChange' ),
useDefaultDescription = allowReferenceChange ? getStateOf( event.target.id, 'useDefaultDescription' ) : true;
let text = ( allowReferenceChange && event.target.value.trim() )
|| ( useDefaultDescription && getStateOf( event.target.id, 'defaultDescription' ) )
|| '';
const referenceValue = tsf.escapeString(
tsf.decodeEntities(
tsf.sDoubleSpace(
tsf.sTabs(
tsf.sSingleLine(
text
).trim()
)
)
) );
const changeEvent = new Event( 'change' );
references.forEach( reference => {
// We require the event below when adjusting some states... Don't uncomment this.
// if ( reference.innerHTML = referenceValue ) return;
reference.innerHTML = referenceValue;
// Fires change event. Defered to another thread.
setTimeout( () => { reference.dispatchEvent( changeEvent ) }, 0 );
} );
}
/**
* Updates the title placeholder.
*
* @since 4.0.0
* @since 4.1.0 Now consistently sets a reliable placeholder.
* @access private
*
* @function
* @param {Event} event
*/
const _updatePlaceholder = event => {
event.target.placeholder = _getDescriptionReferences( event.target.id )[0].textContent;
}
/**
* Updates used separator and all examples thereof.
*
* @since 3.0.4 1. Threshold "too long" has been increased from 155 to 300.
* 2. Threshold "far too long" has been increased to 330 from 175.
* @since 3.1.0 Now uses the new guidelines via a filterable function in PHP.
*
* @function
* @param {Event} event
*/
const _updateCounter = event => {
const counter = document.getElementById( `${event.target.id}_chars` ),
reference = _getDescriptionReferences( event.target.id )[0];
if ( ! counter ) return;
tsfC?.updateCharacterCounter( {
e: counter,
text: reference.innerHTML,
field: 'description',
type: 'search',
} );
}
/**
* Updates the pixel counter bound to the input.
*
* @since 4.0.0
* @access private
*
* @function
* @param {Event} event
*/
const _updatePixels = event => {
const pixels = document.getElementById( `${event.target.id}_pixels` ),
reference = _getDescriptionReferences( event.target.id )[0];
if ( ! pixels ) return;
tsfC?.updatePixelCounter( {
e: pixels,
text: reference.innerHTML,
field: 'description',
type: 'search',
} );
}
/**
* Triggers meta description input.
*
* @since 4.0.0
* @since 4.1.0 Now allows for a first parameter to be set.
* @access public
*
* @function
* @param {string} id The input id. When not set, all inputs will be triggered.
*/
const triggerInput = id => {
if ( id ) {
getInputElement( id )?.dispatchEvent( new Event( 'input' ) );
} else {
// We don't want it to loop infinitely. Check element.id value first.
descriptionInputInstances.forEach( element => element.id && triggerInput( element.id ) );
}
}
/**
* Triggers counter updates.
*
* @since 4.0.0
* @since 4.1.0 Now allows for a first parameter to be set.
* @access public
*
* @function
* @param {string} id The input id. When not set, all inputs will be triggered.
*/
const triggerCounter = id => {
if ( id ) {
getInputElement( id )?.dispatchEvent( new CustomEvent( 'tsf-update-description-counter' ) );
} else {
// We don't want it to loop infinitely. Check element.id value first.
descriptionInputInstances.forEach( element => element.id && triggerCounter( element.id ) );
}
}
/**
* Updates placements, placeholders and counters.
*
* @since 4.0.0
* @access private
* @see triggerInput
* @uses _onUpdateCounterTrigger
*
* @function
* @param {Event} event
*/
const _onUpdateDescriptionsTrigger = event => {
_setReferenceDescription( event );
_updatePlaceholder( event );
_onUpdateCounterTrigger( event );
}
/**
* Updates character counters.
*
* @since 4.0.0
* @access private
* @see triggerCounter
*
* @function
* @param {Event} event
*/
const _onUpdateCounterTrigger = event => {
_updateCounter( event );
_updatePixels( event );
}
let _enqueueTriggerInputBuffer = {};
/**
* Triggers meta description input.
*
* @since 4.0.0
* @since 4.1.0 Added first parameter, id.
* @since 4.1.1 Now passes the right parameter to the input event.
* @access public
*
* @function
* @param {string} id The input ID.
*/
const enqueueTriggerInput = id => {
( id in _enqueueTriggerInputBuffer ) && clearTimeout( _enqueueTriggerInputBuffer[ id ] );
_enqueueTriggerInputBuffer[ id ] = setTimeout( () => triggerInput( id ), 1000/60 ); // 60fps
}
/**
* Triggers description update, without affecting tsfAys change listeners.
*
* @since 4.0.0
* @since 4.1.0 Now allows for a first parameter to be set.
* @access public
*
* @function
* @param {Event}
* @param {string} id The input id. When not set, all inputs will be triggered.
*/
const triggerUnregisteredInput = id => {
if ( 'tsfAys' in window ) {
let wereSettingsChanged = tsfAys.areSettingsChanged();
triggerInput( id );
// Only reset if we polluted the change listener, and only if a change wasn't already registered.
if ( ! wereSettingsChanged && tsfAys.areSettingsChanged() )
tsfAys.reset();
} else {
triggerInput( id );
}
}
let _unregisteredTriggerBuffer = {};
/**
* Enqueues unregistered description input triggers.
*
* @since 4.0.0
* @since 4.1.0 Now allows for a first parameter to be set.
* @access public
*
* @function
* @param {string} id The input id. When not set, all inputs will be triggered.
*/
const enqueueUnregisteredInputTrigger = id => {
( id in _unregisteredTriggerBuffer ) && clearTimeout( _unregisteredTriggerBuffer[ id ] );
_unregisteredTriggerBuffer[ id ] = setTimeout( () => triggerUnregisteredInput( id ), 1000/60 ); // 60 fps
}
let prevWidth = window.innerWidth;
/**
* Triggers input event for descriptions in set intervals on window resize.
*
* This only happens if boundaries are surpassed to reduce CPU usage.
* This boundary is 782 pixels, because that forces input fields to change.
* in WordPress.
*
* This happens to all description inputs; as WordPress switches
* from Desktop to Mobile view at 782 pixels.
*
* @since 4.0.0
* @access private
* @see ...\wp-admin\js\common.js
*
* @function
*/
const _doResize = () => {
const width = window.innerWidth;
if ( prevWidth < width ) {
if ( prevWidth <= 782 && width >= 782 ) {
triggerUnregisteredInput();
}
} else {
if ( prevWidth >= 782 && width <= 782 ) {
triggerUnregisteredInput();
}
}
prevWidth = width;
}
/**
* Reinitializes the description input action callbacks.
*
* @since 4.0.0
* @access private
*
* @function
* @param {Element} descriptionInput
*/
const _loadDescriptionActions = descriptionInput => {
if ( ! descriptionInput instanceof Element ) return;
descriptionInput.addEventListener( 'input', _onUpdateDescriptionsTrigger );
descriptionInput.addEventListener( 'tsf-update-description-counter', _onUpdateCounterTrigger );
enqueueUnregisteredInputTrigger( descriptionInput.id );
}
/**
* Initializes the description environment.
*
* @since 4.1.0
* @since 4.1.1 No longer passes the event to the enqueueUnregisteredInputTrigger() callback.
* @access private
*
* @function
*/
const _initAllDescriptionActions = () => {
// Triggers input changes on resize after hitting thresholds.
window.addEventListener( 'tsf-resize', _doResize );
// When counters are updated, trigger an input; which will reassess them.
window.addEventListener( 'tsf-counter-updated', () => enqueueUnregisteredInputTrigger() );
}
return Object.assign( {
/**
* Initialises all aspects of the scripts.
* You shouldn't call this.
*
* @since 4.0.0
* @access protected
*
* @function
*/
load: () => {
document.body.addEventListener( 'tsf-onload', _initAllDescriptionActions );
}
}, {
setInputElement,
getInputElement,
getStateOf,
updateStateOf,
updateStateAll,
triggerCounter,
triggerInput,
enqueueTriggerInput,
triggerUnregisteredInput,
enqueueUnregisteredInputTrigger, // this should've been enqueueTriggerUnregisteredInput...
} );
}();
window.tsfDescription.load();