/
var
/
www
/
barefootlaw.org
/
wp-content
/
plugins
/
autodescription
/
lib
/
js
/
Upload File
HOME
/** * 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, }; }();