Current File : /home/honehdyv/readbtooom.com/wp-content/plugins/autodescription/lib/js/social.js |
/**
* This file holds The SEO Framework plugin's JS code for the Social Input Settings.
* 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 tsfSocial values in an object to avoid polluting global namespace.
*
* @since 4.0.0
* @since 4.2.0 1. Removed 'updateState', and 'getState' without deprecation.
* 2. Can now handle multiple social input elements on the same page.
*
* @constructor
*/
window.tsfSocial = function() {
/**
* @since 4.2.0
* @access private
* @type {(Map<string,{
* group: string,
* inputs: {ogTitle:Element,twTitle:Element,ogDesc:Element,twDesc:Element}
* refs: {titleInput:Element,descInput:Element,title:Element,titleNa:Element,desc:Element}
* }>)} The input element instances.
*/
const inputInstances = new Map();
/**
* @since 4.2.0
* @access private
* @type {(Object<string,Object<string,*>)} the query state.
*/
const states = {};
/**
* Ticks a state change, which may propagate further events.
*
* @since 4.2.0
* @access private
*
* @function
* @param {string} group
* @param {string} part
*/
const _tickState = ( group, part ) => {
switch ( part ) {
case 'addAdditions':
let titleRef = getInputInstance( group ).refs.title.dataset?.for;
titleRef && tsfTitle.enqueueUnregisteredInputTrigger( titleRef );
break;
default:
break;
}
}
/**
* Returns state of ID.
*
* @since 4.2.0
* @access public
*
* @function
* @param {string} group The group ID.
* @param {(string|undefined)} part The part to return. Leave empty to return the whole state.
* @return {(Object<string, *>)|*|null}
*/
const getStateOf = ( group, part ) => part ? states[ group ]?.[ part ] : states[ group ];
/**
* Updates state of ID.
*
* There's no need to escape the input, it may be double-escaped if you do so.
*
* @since 4.2.0
* @access public
* @TODO Allow part to be string[], so we can get 'substates'?
*
* @function
* @param {string} group The group ID.
* @param {string} part The state index to change.
* @param {*} value The value to set the state to.
*/
const updateStateOf = ( group, part, value ) => {
if ( states[ group ][ part ] === value ) return;
states[ group ][ part ] = value;
_tickState( group, part );
}
/**
* Updates state of all elements.
*
* There's no need to escape the input, it may be double-escaped if you do so.
*
* @since 4.2.0
* @access public
*
* @function
* @param {string} part The state index to change.
* @param {*} value The value to set the state to.
* @param {string|string[]} except The input group IDs to exclude from updates.
*/
const updateStateAll = ( part, value, except ) => {
except = Array.isArray( except ) ? except : [ except ];
inputInstances.forEach( ( { group, inputs, refs } ) => {
if ( except.includes( group ) ) return;
updateStateOf( group, part, value );
} );
}
/**
* Sets input group for all listeners. Must be called prior interacting with this object.
* Resets the state for the group ID.
*
* @since 4.2.0
* @access public
*
* @function
* @param {string} group The group ID.
* @param {string} titleRef The group's title reference ID.
* @param {string} descRef The group's description reference ID.
*/
const setInputInstance = ( group, titleRef, descRef ) => {
const _getElement = type => document.querySelector(
`[data-tsf-social-group="${group}"][data-tsf-social-type="${type}"]`
);
const inputs = {
ogTitle: _getElement( 'ogTitle' ),
twTitle: _getElement( 'twTitle' ),
ogDesc: _getElement( 'ogDesc' ),
twDesc: _getElement( 'twDesc' ),
}
const refs = {
titleInput: document.getElementById( titleRef ),
descInput: document.getElementById( descRef ),
title: document.getElementById( `tsf-title-reference_${titleRef}` ),
titleNa: document.getElementById( `tsf-title-noadditions-reference_${titleRef}` ),
desc: document.getElementById( `tsf-description-reference_${descRef}` ),
}
inputInstances.set( group, { group, inputs, refs } );
// Should we do 3x4 or 4x3? For now, it doesn't matter. TODO reconsider?
states[ group ] = {
defaults: {
ogTitle: '',
twTitle: '',
ogDesc: '',
twDesc: '',
},
inputLocks: {
ogTitle: false,
twTitle: false,
ogDesc: false,
twDesc: false,
},
placeholderLocks: {
ogTitle: false,
twTitle: false,
ogDesc: false,
twDesc: false,
},
}
_loadTitleActions( group );
_loadDescriptionActions( group );
return getInputInstance( group );
}
/**
* Gets input element, if exists.
*
* @since 4.2.0
* @access public
*
* @function
* @param {string} group The group ID.
*/
const getInputInstance = group => inputInstances.get( group );
/**
* Loads Title actions for group.
*
* @since 4.2.0
* @access private
*
* @function
* @param {string} group The group ID.
*/
const _loadTitleActions = group => {
const { inputs, refs } = getInputInstance( group );
const getState = part => getStateOf( group, part );
function* _generateActiveValue( what ) {
const locks = getState( 'inputLocks' ),
phLocks = getState( 'placeholderLocks' );
switch ( what ) {
case 'twitter':
yield locks.twTitle
? getState( 'defaults' ).twTitle
: inputs.twTitle.value.trim();
if ( locks.twTitle || phLocks.twTitle ) {
yield getState( 'defaults' ).twTitle;
break;
}
case 'og':
yield locks.ogTitle
? getState( 'defaults' ).ogTitle
: inputs.ogTitle.value.trim();
if ( locks.ogTitle || phLocks.ogTitle ) {
yield getState( 'defaults' ).ogTitle;
break;
}
case 'meta':
// All is handled by ref due to the title's complexity.
case 'ref':
if ( getState( 'addAdditions' ) ) {
yield refs.title.innerHTML;
} else {
yield refs.titleNa.innerHTML;
}
break;
}
}
const getActiveValue = what => {
const generator = _generateActiveValue( what );
let val = '';
while ( 'undefined' !== typeof val && ! val.length ) {
val = generator.next().value;
if ( val?.length )
val = tsf.sDoubleSpace(
tsf.sTabs(
tsf.sSingleLine(
val
)
)
);
}
return val?.length ? val : '';
}
const setPlaceholders = () => {
const locks = getState( 'inputLocks' ),
phLocks = getState( 'placeholderLocks' );
// Security OK. All getActiveValue is escaped.
inputs.ogTitle.placeholder
= locks.ogTitle || phLocks.ogTitle
? tsf.decodeEntities( getState( 'defaults' ).ogTitle )
: tsf.decodeEntities( getActiveValue( 'meta' ) );
inputs.twTitle.placeholder
= locks.twTitle || phLocks.twTitle
? tsf.decodeEntities( getState( 'defaults' ).twTitle )
: tsf.decodeEntities( getActiveValue( 'og' ) );
}
const updateCounter = ( target, text, type ) => {
let counter = document.getElementById( `${target.id}_chars` );
counter && tsfC.updateCharacterCounter( {
e: counter,
text: text,
field: 'title',
type: type,
} );
}
const updateSocialCounters = () => {
updateCounter( inputs.ogTitle, getActiveValue( 'og' ), 'opengraph' );
updateCounter( inputs.twTitle, getActiveValue( 'twitter' ), 'twitter' );
}
let updateRefTitleBuffer = void 0;
const updateRefTitle = () => {
clearTimeout( updateRefTitleBuffer );
updateRefTitleBuffer = setTimeout( () => {
setPlaceholders();
updateSocialCounters();
}, 1000/60 ); // 60fps
};
refs.title.addEventListener( 'change', updateRefTitle );
refs.titleNa.addEventListener( 'change', updateRefTitle );
let updateTitleBuffer = void 0;
const updateTitle = () => {
clearTimeout( updateTitleBuffer );
updateTitleBuffer = setTimeout( () => {
setPlaceholders();
updateSocialCounters();
}, 1000/60 ); // 60fps
}
inputs.ogTitle.addEventListener( 'input', updateTitle );
inputs.twTitle.addEventListener( 'input', updateTitle );
}
/**
* Loads Title actions for group.
*
* @since 4.2.0
* @access private
*
* @function
* @param {string} group The group ID.
*/
const _loadDescriptionActions = group => {
const { inputs, refs } = getInputInstance( group );
const getState = part => getStateOf( group, part );
// We use context here because the description guidelines differ per social media embed.
function* _generateActiveValue( what, context ) {
const locks = getState( 'inputLocks' ),
phLocks = getState( 'placeholderLocks' );
switch ( what ) {
case 'twitter':
yield locks.twDesc
? getState( 'defaults' ).twDesc
: inputs.twDesc.value.trim();
if ( locks.twDesc || phLocks.twDesc ) {
yield getState( 'defaults' ).twDesc;
break;
}
// get next if not set.
case 'og':
yield locks.ogDesc
? getState( 'defaults' ).ogDesc
: inputs.ogDesc.value.trim();
if ( locks.ogDesc || phLocks.ogDesc ) {
yield getState( 'defaults' ).ogDesc;
break;
}
// get next if not set.
case 'meta':
if ( ! refs.descInput?.value.length ) {
if ( 'twitter' === context ) {
yield getState( 'defaults' ).twDesc;
} else if ( 'og' === context ) {
yield getState( 'defaults' ).ogDesc;
}
}
// get next if not set.
case 'ref':
yield refs.desc.innerHTML;
break;
}
}
const getActiveValue = ( what, context ) => {
const generator = _generateActiveValue( what, context );
let val = '';
while ( 'undefined' !== typeof val && ! val.length ) {
val = generator.next().value;
if ( val?.length )
val = tsf.sDoubleSpace(
tsf.sTabs(
tsf.sSingleLine(
val
)
)
);
}
return val?.length ? val : '';
}
const setPlaceholders = () => {
const locks = getState( 'inputLocks' ),
phLocks = getState( 'placeholderLocks' );
// Security OK. All getActiveValue is escaped.
inputs.ogDesc.placeholder
= locks.ogDesc || phLocks.ogDesc
? tsf.decodeEntities( getState( 'defaults' ).ogDesc )
: tsf.decodeEntities( getActiveValue( 'meta', 'og' ) );
// Security OK. All getActiveValue is escaped.
inputs.twDesc.placeholder
= locks.twDesc || phLocks.twDesc
? tsf.decodeEntities( getState( 'defaults' ).twDesc )
: tsf.decodeEntities( getActiveValue( 'og', 'twitter' ) );
}
const updateCounter = ( target, text, type ) => {
let counter = document.getElementById( `${target.id}_chars` );
counter && tsfC.updateCharacterCounter( {
e: counter,
text: text,
field: 'description',
type: type,
} );
}
const updateSocialCounters = () => {
updateCounter( inputs.ogDesc, getActiveValue( 'og', 'og' ), 'opengraph' );
updateCounter( inputs.twDesc, getActiveValue( 'twitter', 'twitter' ), 'twitter' );
}
let updateRefDescBuffer = void 0;
const updateRefDesc = () => {
clearTimeout( updateRefDescBuffer );
updateRefDescBuffer = setTimeout( () => {
setPlaceholders();
updateSocialCounters();
}, 1000/60 ); // 60fps
};
refs.desc.addEventListener( 'change', updateRefDesc );
let updateDescBuffer = void 0;
const updateDesc = () => {
clearTimeout( updateDescBuffer );
updateDescBuffer = setTimeout( () => {
setPlaceholders();
updateSocialCounters();
}, 1000/60 ); // 60fps
}
inputs.ogDesc.addEventListener( 'input', updateDesc );
inputs.twDesc.addEventListener( 'input', updateDesc );
}
return {
getStateOf,
updateStateOf,
updateStateAll,
setInputInstance,
getInputInstance,
};
}();