File: /home/mmickelson/jennysmasks.com/wp-content/plugins/boldgrid-easy-seo/assets/js/bgseo.js
// Setup the BOLDGRID Object if it doesn't exist already.
var BOLDGRID = BOLDGRID || {};
// Create the BOLDGRID.SEO object.
BOLDGRID.SEO = {
// Add the analysis report to the BOLDGRID.SEO object.
report : {
bgseo_visibility : {},
bgseo_keywords : {},
bgseo_meta : {},
rawstatistics : {},
textstatistics : {},
},
};
( function ( $ ) {
'use strict';
/**
* Registers dashboard display as control.
*
* @since 1.4
*/
butterbean.views.register_control( 'dashboard', {
// Wrapper element for the control.
tagName : 'div',
// Custom attributes for the control wrapper.
attributes : function() {
return {
'id' : 'butterbean-control-' + this.model.get( 'name' ),
'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' )
};
},
initialize : function() {
$( window ).bind( 'bgseo-report', _.bind( this.setAnalysis, this ) );
this.bgseo_template = wp.template( 'butterbean-control-dashboard' );
// Bind changes so that the view is re-rendered when the model changes.
_.bindAll( this, 'render' );
this.model.bind( 'change', this.render );
},
/**
* Get the results report for a given section.
*
* @since 1.3.1
*
* @param {Object} section The section name to get report for.
*
* @returns {Object} report The report for the section to display.
*/
results : function( data ) {
var report = {};
_.each( data, function( key ) {
_.extend( report, key );
});
return report;
},
/**
* Gets the analysis for the section from the reporter.
*
* This is bound to the bgseo-report event, and will process
* the report and add only the analysis for the current section displayed.
*
* @since 1.3.1
*
* @param {Object} report The full report as it's updated by reporter.
*/
setAnalysis: function( e, report ) {
var sectionScore,
section = this.model.get( 'section' ),
data = _.pick( report, section );
// Get each of the analysis results to pass for template rendering.
this.sectionReport = this.results( data );
// Set the section's report in the model's attributes.
this.model.set( 'analysis', this.sectionReport );
// Get score for each section, and set a status for sections.
_( report ).each( function( section ) {
// sectionScore should be set.
if ( ! _.isUndefined ( section.sectionScore ) ) {
sectionScore = BOLDGRID.SEO.Sections.score( section );
_( section ).extend( sectionScore );
}
});
// Add the overview score to report.
_( report.bgseo_keywords ).extend({
overview : {
score : BOLDGRID.SEO.Dashboard.overviewScore( report ),
},
});
// Get the status based on the overview score, and add to report.
_( report.bgseo_keywords.overview ).extend({
status : BOLDGRID.SEO.Dashboard.overviewStatus( report.bgseo_keywords.overview.score ),
});
// Set the nav highlight indicator for each section's tab.
BOLDGRID.SEO.Sections.navHighlight( report );
BOLDGRID.SEO.Sections.overviewStatus( report );
},
// Renders the control template.
render : function() {
// Only render template if model is active.
if ( this.model.get( 'active' ) )
this.el.innerHTML = this.bgseo_template( this.model.toJSON() );
return this;
},
});
})( jQuery );
( function ( $ ) {
'use strict';
/**
* Registers the keywords display as a control.
*
* @since 1.4
*/
butterbean.views.register_control( 'keywords', {
// Wrapper element for the control.
tagName : 'div',
// Custom attributes for the control wrapper.
attributes : function() {
return {
'id' : 'butterbean-control-' + this.model.get( 'name' ),
'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' )
};
},
initialize : function() {
$( window ).bind( 'bgseo-report', _.bind( this.setAnalysis, this ) );
this.bgseo_template = wp.template( 'butterbean-control-keywords' );
// Bind changes so that the view is re-rendered when the model changes.
_.bindAll( this, 'render' );
this.model.bind( 'change', this.render );
},
setAnalysis: function( e, report ) {
this.model.set( report );
},
// Renders the control template.
render : function() {
// Only render template if model is active.
if ( this.model.get( 'active' ) )
this.el.innerHTML = this.bgseo_template( this.model.toJSON() );
return this;
},
});
})( jQuery );
var BOLDGRID = BOLDGRID || {};
BOLDGRID.SEO = BOLDGRID.SEO || {};
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Util.
*
* This will contain any utility functions needed across
* all classes.
*
* @since 1.3.1
*/
api.Util = {
/**
* Initialize Utilities.
*
* @since 1.3.1
*/
init : function () {
_.mixin({
/**
* Return a copy of the object only containing the whitelisted properties.
* Nested properties are concatenated with dots notation.
*
* Example:
* a = { min: 0.5, max : 2.5 };
* _.modifyObject( a, function( item ){ return item * item; });
*
* Returns:
* { min: 0.25, max : 6.25 };
*
* @since 1.3.1
*
* @param obj
*
* @returns {Object} Modified object.
*/
modifyObject : function( object, iteratee ) {
return _.object( _.map( object, function( value, key ) {
return [ key, iteratee( value ) ];
}));
},
/**
* Return a copy of the object only containing the whitelisted properties.
* Nested properties are concatenated with dots notation.
*
* Example:
* a = {a:'a', b:{c:'c', d:'d', e:'e'}};
* _.pickDeep(a, 'b.c','b.d')
*
* Returns:
* {b:{c:'c',d:'d'}}
*
* @since 1.3.1
*
* @param obj
*
* @returns {Object} copy Object containing only properties requested.
*/
pickDeep : function( obj ) {
var copy = {},
keys = Array.prototype.concat.apply( Array.prototype, Array.prototype.slice.call( arguments, 1 ) );
this.each( keys, function( key ) {
var subKeys = key.split( '.' );
key = subKeys.shift();
if ( key in obj ) {
// pick nested properties
if( subKeys.length > 0 ) {
// extend property (if defined before)
if( copy[ key ] ) {
_.extend( copy[ key ], _.pickDeep( obj[ key ], subKeys.join( '.' ) ) );
}
else {
copy[ key ] = _.pickDeep( obj[ key ], subKeys.join( '.' ) );
}
}
else {
copy[ key ] = obj[ key ];
}
}
});
return copy;
},
});
/**
* Usage: ( n ).isBetween( min, max )
*
* Gives you bool response if number is within the minimum
* and maximum numbers specified for the range.
*
* @since 1.3.1
*
* @param {Number} min Minimum number in range to check.
* @param {Number} max Maximum number in range to check.
*
* @returns {bool} Number is/isn't within range passed in params.
*/
if ( ! Number.prototype.isBetween ) {
Number.prototype.isBetween = function( min, max ) {
if ( _.isUndefined( min ) ) min = 0;
if ( _.isUndefined( max ) ) max = 0;
var newMax = Math.max( min, max );
var newMin = Math.min( min, max );
return this > newMin && this < newMax;
};
}
/**
* Usage: ( n ).rounded( digits )
*
* Rounds a number to the closest decimal you specify.
*
* @since 1.3.1
*
* @param {Number} number Number to round.
* @param {Number} digits how many decimal places to round to.
*
* @returns {Number} rounded The number rounded to specified digits.
*/
if ( ! Number.prototype.rounded ) {
Number.prototype.rounded = function( digits ) {
if ( _.isUndefined( digits ) ) digits = 0;
var multiple = Math.pow( 10, digits );
var rounded = Math.round( this * multiple ) / multiple;
return rounded;
};
}
if ( ! String.prototype.printf ) {
String.prototype.printf = function() {
var newStr = this, i = 0;
while ( /%s/.test( newStr ) ){
newStr = newStr.replace( "%s", arguments[i++] );
}
return newStr;
};
}
/**
* Function that counts occurrences of a substring in a string;
*
* @param {String} string The string
* @param {String} subString The sub string to search for
* @param {Boolean} [allowOverlapping] Optional. (Default:false)
*
* @returns {Number} n The number of times a substring appears in a string.
*/
if ( ! String.prototype.occurences ) {
String.prototype.occurences = function( needle, allowOverlapping ) {
needle += "";
if ( needle.length <= 0 ) return ( this.length + 1 );
var n = 0,
pos = 0,
step = allowOverlapping ? 1 : needle.length;
while ( true ) {
pos = this.indexOf( needle, pos );
if ( pos >= 0 ) {
++n;
pos += step;
} else break;
}
return n;
};
}
},
};
self = api.Util;
})( jQuery );
( function() {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
api.Words = {
init : function( settings ) {
var key,
shortcodes;
if ( settings ) {
for ( key in settings ) {
if ( settings.hasOwnProperty( key ) ) {
self.settings[ key ] = settings[ key ];
}
}
}
shortcodes = self.settings.l10n.shortcodes;
if ( shortcodes && shortcodes.length ) {
self.settings.shortcodesRegExp = new RegExp( '\\[\\/?(?:' + shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' );
}
},
settings : {
HTMLRegExp: /<\/?[a-z][^>]*?>/gi,
HTMLcommentRegExp: /<!--[\s\S]*?-->/g,
spaceRegExp: / | /gi,
HTMLEntityRegExp: /&\S+?;/g,
connectorRegExp: /--|\u2014/g,
removeRegExp: new RegExp( [
'[',
// Basic Latin (extract)
'\u0021-\u0040\u005B-\u0060\u007B-\u007E',
// Latin-1 Supplement (extract)
'\u0080-\u00BF\u00D7\u00F7',
// General Punctuation
// Superscripts and Subscripts
// Currency Symbols
// Combining Diacritical Marks for Symbols
// Letterlike Symbols
// Number Forms
// Arrows
// Mathematical Operators
// Miscellaneous Technical
// Control Pictures
// Optical Character Recognition
// Enclosed Alphanumerics
// Box Drawing
// Block Elements
// Geometric Shapes
// Miscellaneous Symbols
// Dingbats
// Miscellaneous Mathematical Symbols-A
// Supplemental Arrows-A
// Braille Patterns
// Supplemental Arrows-B
// Miscellaneous Mathematical Symbols-B
// Supplemental Mathematical Operators
// Miscellaneous Symbols and Arrows
'\u2000-\u2BFF',
// Supplemental Punctuation
'\u2E00-\u2E7F',
']'
].join( '' ), 'g' ),
astralRegExp: /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// regex tested : https://regex101.com/r/vHAwas/2
wordsRegExp: /.+?\s+/g,
characters_excluding_spacesRegExp: /\S/g,
characters_including_spacesRegExp: /[^\f\n\r\t\v\u00AD\u2028\u2029]/g,
l10n: window.wordCountL10n || {}
},
words : function( text, type ) {
var count = 0;
type = type || self.settings.l10n.type;
if ( type !== 'characters_excluding_spaces' && type !== 'characters_including_spaces' ) {
type = 'words';
}
if ( text ) {
text = text + '\n';
text = text.replace( self.settings.HTMLRegExp, '\n' );
text = text.replace( self.settings.HTMLcommentRegExp, '' );
if ( self.settings.shortcodesRegExp ) {
text = text.replace( self.settings.shortcodesRegExp, '\n' );
}
text = text.replace( self.settings.spaceRegExp, ' ' );
if ( type === 'words' ) {
text = text.replace( self.settings.HTMLEntityRegExp, '' );
text = text.replace( self.settings.connectorRegExp, ' ' );
text = text.replace( self.settings.removeRegExp, '' );
} else {
text = text.replace( self.settings.HTMLEntityRegExp, 'a' );
text = text.replace( self.settings.astralRegExp, 'a' );
}
text = text.match( self.settings[ type + 'RegExp' ] );
if ( text ) {
count = text;
}
}
return count;
},
};
self = api.Words;
} )();
( function( $, counter ) {
'use strict';
var self, api;
api = BOLDGRID.SEO;
/**
* Handle tracking of wordcount.
*
* @since 1.6.0
*/
api.Wordcount = {
/**
* Number of words in the content.
*
* @since 1.6.0
*
* @type {Number}
*/
count: 0,
/**
* List of words on the page.
*
* @since 1.6.0
*
* @type {array}
*/
words: [],
/**
* When the page loads, run the update methods.
*
* @since 1.6.0
*/
init : function () {
$( self.update );
},
/**
* Update this classes word count metrics.
*
* @since 1.6.0
*/
update : function () {
var count,
words,
text = api.Editor.ui.getRawText();
count = counter.count( text );
words = BOLDGRID.SEO.Words.words( text );
if ( count !== self.count ) {
api.Editor.element.trigger( 'bgseo-analysis', [{ words : words, count : count }] );
}
self.words = words;
self.count = count;
}
};
self = api.Wordcount;
} )( jQuery, new wp.utils.WordCounter() );
( function ( $ ) {
'use strict';
var self;
/**
* BoldGrid SEO Admin.
*
* This is responsible for setting the counters for the SEO Title &
* Description tab.
*
* @since 1.2.1
*/
BOLDGRID.SEO.Admin = {
/**
* Initialize Word Count.
*
* @since 1.2.1
*/
init : function () {
$( document ).ready( function() {
self._setWordCounts();
});
},
/**
* Get the word count of a metabox field.
*
* @since 1.2.1
*
* @param {Object} $element The element to apply the word counter to.
*/
wordCount : function( $element ) {
var limit = $element.attr( 'maxlength' ),
$counter = $( '<span />', {
'class' : 'boldgrid-seo-meta-counter',
'style' : 'font-weight: bold'
}),
$container = $( '<div />', {
'class' : 'boldgrid-seo-meta-countdown boldgrid-seo-meta-extra',
'html' : ' characters left'
});
if ( limit ) {
$element
.removeAttr( 'maxlength' )
.after( $container.prepend( $counter ) )
.on( 'keyup focus' , function() {
self.setCounter( $counter, $element, limit );
});
}
self.setCounter( $counter, $element, limit );
},
/**
* Set the colors of the count to reflect ideal lengths.
*
* @since 1.2.1
*
* @param {Object} $counter New element to create for counter.
* @param {Object} $target Element to check the input value of.
* @param {Number} limit The maxlength of the input to calculate on.
*/
setCounter : function( $counter, $target, limit ) {
var text = $target.val(),
chars = text.length;
$counter.html( limit - chars );
if ( $target.attr( 'id' ) === 'boldgrid-seo-field-meta_description' ) {
if ( chars > limit ) {
$counter.css( { 'color' : '#EA4335' } );
} else if ( chars.isBetween( 0, _bgseoContentAnalysis.seoDescription.length.okScore ) ) {
$counter.css( { 'color' : '#FBBC05' } );
} else if ( chars.isBetween( _bgseoContentAnalysis.seoDescription.length.okScore -1, _bgseoContentAnalysis.seoDescription.length.goodScore + 1 ) ) {
$counter.css( { 'color' : '#34A853' } );
} else {
$counter.css( { 'color' : 'black' } );
}
} else {
if ( chars > limit ) {
$counter.css( { 'color' : '#EA4335' } );
} else if ( chars.isBetween( 0, _bgseoContentAnalysis.seoTitle.length.okScore ) ) {
$counter.css( { 'color' : '#FBBC05' } );
} else if ( chars > _bgseoContentAnalysis.seoTitle.length.okScore - 1 ) {
$counter.css( { 'color' : '#34A853' } );
} else {
$counter.css( { 'color' : 'black' } );
}
}
},
/**
* Set the word counts for each field in the SEO Title & Description Tab.
*
* @since 1.2.1
*/
_setWordCounts : function() {
// Apply our wordcount counter to the meta title and meta description textarea fields.
$( '#boldgrid-seo-field-meta_title, #boldgrid-seo-field-meta_description' )
.each( function() {
self.wordCount( $( this ) );
});
},
};
self = BOLDGRID.SEO.Admin;
})( jQuery );
( function ( $ ) {
'use strict';
var self, api;
api = BOLDGRID.SEO;
/**
* BoldGrid TinyMCE Analysis.
*
* This is responsible for generating the actual reports
* displayed within the BoldGrid SEO Dashboard when the user
* is on a page or a post.
*
* @since 1.3.1
*/
api.TinyMCE = {
/**
* Selector to find editor id.
*
* @since 1.6.0
*
* @type {String}
*/
selector : '#content',
/**
* Selector to find preview button.
*
* @since 1.6.0
*
* @type {String}
*/
previewSelector : '#preview-action > .preview.button',
/**
* Initialize TinyMCE Content.
*
* @since 1.3.1
*/
setup : function () {
$( document ).ready( function() {
self._setupWordCount();
self.editorChange();
});
},
/**
* Gets the content from TinyMCE or the text editor for analysis.
*
* @since 1.3.1
*
* @returns {Object} content Contains content in raw and text formats.
*/
getContent : function() {
var content,
tinymce = self.getTinymce();
if ( tinymce ) {
content = tinymce.getContent();
} else {
content = api.Editor.element.val();
// Remove newlines and carriage returns.
content = content.replace( /\r?\n|\r/g, '' );
}
var rawContent = $.parseHTML( content );
// Stores raw and stripped down versions of the content for analysis.
content = {
'raw': rawContent,
'text': api.Editor.stripper( content.toLowerCase() ),
};
return content;
},
/**
* Get the raw text from the editor.
*
* @since 1.6.0
*
* @return {string} Editor content.
*/
getRawText: function() {
var text,
contentEditor = self.getTinymce();
if ( ! contentEditor || contentEditor.isHidden() ) {
text = api.Editor.element.val();
} else {
text = contentEditor.getContent( { format: 'raw' } );
// Sometimes, on initial page load, the 'getContent' method doesn't return anything.
text = text ? text : api.Editor.element.val();
}
return text;
},
/**
* Get Tinymce instance.
*
* @since 1.6.0
*
* @return {object} Active wp editor tinymce.
*/
getTinymce: function() {
return 'undefined' !== typeof tinyMCE ? tinyMCE.get( wpActiveEditor ) : null;
},
/**
* Listens for changes made in the text editor mode.
*
* @since 1.3.1
*
* @returns {string} text The new content to perform analysis on.
*/
editorChange: function() {
var text, targetId;
$( '#content.wp-editor-area' ).on( 'input propertychange paste nodechange', function() {
targetId = $( this ).attr( 'id' );
text = self.wpContent( targetId );
});
return text;
},
/**
* This gets the content from the TinyMCE Visual editor.
*
* @since 1.3.1
*
* @returns {string} text
*/
tmceChange: function( e ) {
var text, targetId;
targetId = e.target.id;
text = self.wpContent( targetId );
return text;
},
/**
* Is this a new Post?
*
* @since 1.6.0
*
* @return {boolean} Is this post-new.php?
*/
isNewPost : function () {
return ! $( '#sample-permalink' ).length;
},
/**
* Checks which editor is the active editor.
*
* After checking the editor, it will obtain the content and trigger
* the report generation with the new user input.
*
* @since 1.3.1
*/
wpContent : function( targetId ) {
var text = {};
switch ( targetId ) {
// Grab text from TinyMCE Editor.
case 'tinymce' :
// Only do this if page/post editor has TinyMCE as active editor.
if ( self.getTinymce() )
// Define text as the content of the current TinyMCE instance.
text = self.getTinymce().getContent();
break;
case 'content' :
text = api.Editor.element.val();
text = text.replace( /\r?\n|\r/g, '' );
break;
}
// Convert raw text to DOM nodes.
var rawText = $.parseHTML( text );
text = {
'raw': rawText,
'text': api.Editor.stripper( text.toLowerCase() ),
};
// Trigger the text analysis for report.
api.Editor.element.trigger( 'bgseo-analysis', [text] );
},
/**
* Bind events to the editor input and update the wordcount class.
*
* @since 1.6.0
*/
_setupWordCount : function() {
var debouncedCb = _.debounce( api.Wordcount.update, 1000 );
$( document ).on( 'tinymce-editor-init', function( event, editor ) {
if ( editor.id !== 'content' ) {
return;
}
editor.on( 'AddUndo keyup', debouncedCb );
} );
api.Editor.element.on( 'input keyup', debouncedCb );
}
};
self = api.TinyMCE;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Content Analysis.
*
* This is responsible for general analysis of the user's content.
*
* @since 1.3.1
*/
api.ContentAnalysis = {
/**
* Content Length Score.
*
* This is responsible for the user's content length scoring. The content
* length for this method is based on the word count, and not character
* counts.
*
* @since 1.3.1
*
* @param {Number} contentLength The length of the content to provide score on.
*
* @returns {Object} msg Contains the status indicator color and message.
*/
seoContentLengthScore: function( contentLength ) {
var content, displayed, msg = {};
// Cast to int to avoid errors in scoring.
contentLength = Number( contentLength );
// Content var.
content = _bgseoContentAnalysis.content.length;
// Displayed Message.
displayed = content.contentLength.printf( contentLength ) + ' ';
if ( contentLength === 0 ) {
msg = {
status: 'red',
msg: content.badEmpty,
};
}
if ( contentLength.isBetween( 0, content.badShortScore ) ) {
msg = {
status: 'red',
msg: displayed + content.badShort,
};
}
if ( contentLength.isBetween( content.badShortScore -1, content.okScore ) ) {
msg = {
status: 'yellow',
msg: displayed + content.ok,
};
}
if ( contentLength > content.okScore -1 ) {
msg = {
status: 'green',
msg: displayed + content.good,
};
}
return msg;
},
/**
* Checks if user has any images in their content.
*
* This provides a status and message if the user has included an
* image in their content for their page/post running analysis.
*
* @since 1.3.1
*
* @param {Number} imageLength Count of images found within content.
*
* @returns {Object} msg Contains the status indicator color and message.
*/
seoImageLengthScore: function( imageLength ) {
var msg = {
status: 'green',
msg: _bgseoContentAnalysis.image.length.good,
};
if ( ! imageLength ) {
msg = {
status: 'red',
msg: _bgseoContentAnalysis.image.length.bad,
};
}
return msg;
},
/**
* Get count of keywords used in content.
*
* This checks the content for occurences of the keyword used throughout.
*
* @since 1.3.1
*
* @param {string} content The content to search for the keyword in.
*
* @returns {Number} Count of times keyword appears in content.
*/
keywords : function( content ) {
var keyword = api.Keywords.getKeyword();
return content.occurences( keyword );
},
};
self = api.ContentAnalysis;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Dashboard.
*
* This is responsible for any Dashboard section specific functionality.
*
* @since 1.3.1
*/
api.Dashboard = {
/**
* This gets the overview score.
*
* Number is a percentage.
*
* @since 1.3.1
*
* @param {Object} report The BoldGrid SEO Analysis report.
*
* @returns {Number} The rounded percentage value for overall score.
*/
overviewScore : function( report ) {
var max,
total = self.totalScore( report ),
sections = _.size( butterbean.models.sections );
max = sections * 2;
return ( total / max * 100 ).rounded( 2 );
},
/**
* This gets the overview status.
*
* @since 1.3.1
*
* @param {Number} score The BoldGrid SEO overview status score.
*
* @returns {string} The status indicator color of the overall scoring.
*/
overviewStatus : function( score ) {
var status;
// Default overview status.
status = 'green';
// If status is below 40%.
if ( score < 40 ) {
status = 'red';
}
// Status is 40% - 75%.
if ( score.isBetween( 39, 76 ) ) {
status = 'yellow';
}
return status;
},
/**
* Get the combined statuses for each section in BoldGrid SEO metabox.
*
* @since 1.3.1
*
* @param {Object} report The BoldGrid SEO Analysis report.
*
* @returns {Object} status The combined statuses for all sections.
*/
getStatuses : function( report ) {
var status = {};
_.each( butterbean.models.sections, function( section ) {
var score, name = section.get( 'name' );
score = report[name].sectionStatus;
status[name] = score;
_( status[name] ).extend( score );
});
return status;
},
/**
* Assigns numbers to represent the statuses.
*
* @since 1.3.1
*
* @param {Object} report The BoldGrid SEO Analysis report.
*
* @returns {Object} score The numerical values based on status rank.
*/
assignNumbers : function( report ) {
var score, statuses;
statuses = self.getStatuses( report );
// Map strings into score values.
score = _.mapObject( statuses, function( status ) {
var score;
if ( status === 'red' ) score = 0;
if ( status === 'yellow' ) score = 1;
if ( status === 'green' ) score = 2;
return score;
});
return score;
},
/**
* Combines all the status scores into a final sum.
*
* @since 1.3.1
*
* @param {Object} report The BoldGrid SEO Analysis report.
*
* @returns {Object} total The total overall numerical value for statuses.
*/
totalScore : function( report ) {
var total, statuses = self.assignNumbers( report );
total = _( statuses ).reduce( function( initial, number ) {
return initial + number;
}, 0 );
return total;
}
};
self = api.Dashboard;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Description.
*
* This is responsible for the SEO Description Grading.
*
* @since 1.3.1
*/
api.Description = {
/**
* Initialize SEO Description Analysis.
*
* @since 1.3.1
*/
init : function () {
$( document ).ready( self.onReady );
},
/**
* Sets up event listeners and selector cache in settings on document ready.
*
* @since 1.3.1
*/
onReady : function() {
self.getSettings();
self._description();
},
/**
* Cache selectors
*
* @since 1.3.1
*/
getSettings : function() {
self.settings = {
description : $( '#boldgrid-seo-field-meta_description' ),
};
},
/**
* Sets up event listener for changes made to the SEO Description.
*
* Listens for changes being made to the SEO Description, and then
* triggers the reporter to be updated with new status/score.
*
* @since 1.3.1
*/
_description : function() {
// Listen for changes to input value.
self.settings.description.on( 'input propertychange paste', _.debounce( function() {
$( this ).trigger( 'bgseo-analysis', [{ descLength : self.settings.description.val().length }] );
}, 1000 ) );
},
/**
* Gets the SEO Description.
*
* @since 1.3.1
*
* @returns {Object} description Contains wrapped set with BoldGrid SEO Description.
*/
getDescription : function() {
return self.settings.description;
},
/**
* Gets score of the SEO Description.
*
* Checks the length provided and returns a score and status color
* for the SEO description. This score is based on character count.
*
* @since 1.3.1
*
* @param {Number} descriptionLength Length of the user's SEO Description.
*
* @returns {Object} msg Contains status indicator color and message to update.
*/
descriptionScore : function( descriptionLength ) {
var msg = {}, desc;
desc = _bgseoContentAnalysis.seoDescription.length;
// No description has been entered.
if ( descriptionLength === 0 ) {
msg = {
status: 'red',
msg: desc.badEmpty,
};
}
// Character count is 1-124.
if ( descriptionLength.isBetween( 0, desc.okScore ) ) {
msg = {
status: 'yellow',
msg: desc.ok,
};
}
// Character count is 125-156.
if ( descriptionLength.isBetween( desc.okScore - 1, desc.goodScore + 1 ) ) {
msg = {
status: 'green',
msg: desc.good,
};
}
// Character coutn is over 156.
if ( descriptionLength > desc.goodScore ) {
msg = {
status: 'red',
msg: desc.badLong,
};
}
return msg;
},
/**
* Gets the number of occurences in the SEO Description.
*
* @since 1.3.1
*
* @returns {Number} Frequency that keyword appears in description.
*/
keywords : function() {
var keyword, description;
// Get keyword.
keyword = api.Keywords.getKeyword();
// Get text from input.
description = self.getDescription().val();
// Normalize user input.
description = description.toLowerCase();
return description.occurences( keyword );
},
};
self = api.Description;
})( jQuery );
( function ( $ ) {
'use strict';
var self, api, report;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid Editor Interface.
*
* This class allows us to control which editor interface functions will run.
* On first load the it runs the setup method of whichever ui is currently loaded.
* It then assigns that ui to this classes ui variable for cross api use.
*
* @since 1.6.0
*/
api.Editor = {
/**
* Interface loaded.
*
* @since 1.6.0
*
* @type {object} seo.tinymce or seo-Gutenberg
*/
ui: null,
/**
* WP Element to use to trigger events.
*
* @since 1.6.0
*
* @type {$} Editor jQuery Element.
*/
element: null,
/**
* Setup the correct editor interface.
*
* @since 1.3.1
*/
init : function () {
/*
* Determine if we're in Gutenberg or TinyMCE.
*
* The .block-editor-page class logic comes from a Gutenberg GitHub issue.
*
* @link https://github.com/WordPress/gutenberg/issues/12200
*/
self.ui = document.body.classList.contains( 'block-editor-page' ) ? api.Gutenberg : api.TinyMCE;
self.element = $( self.ui.selector );
self.ui.setup();
self.onloadContent();
},
/**
* Runs actions on window load to prepare for analysis.
*
* This method gets the current editor in use by the user on the
* initial page load ( text editor or visual editor ), and also
* is responsible for creating the iframe preview of the page/post
* so we can get the raw html in use by the template/theme the user
* has activated.
*
* @since 1.3.1
*/
onloadContent : function() {
var text,
editor = $( '#content.wp-editor-area[aria-hidden=false]' );
$( window ).on( 'load bgseo-media-inserted', function() {
var content = self.ui.getContent();
// Get rendered page content from frontend site.
self.getRenderedContent();
// Trigger the content analysis for the content.
_.defer( function() {
self.element.trigger( 'bgseo-analysis', [content] );
} );
} );
},
/**
* Only ajax for preview if permalink is available. This only
* impacts "New" page and posts. To counter
* this we will disable the checks made until the content has had
* a chance to be updated. We will store the found headings minus
* the initial found headings in the content, so we know what the
* template has in use on the actual rendered page.
*
* @since 1.3.1
*
* @returns null No return.
*/
getRenderedContent : function() {
var renderedContent, preview;
// Get the preview url from WordPress.
preview = $( api.Editor.ui.previewSelector ).attr( 'href' );
if ( ! api.Editor.ui.isNewPost() ) {
// Only run this once after the initial iframe has loaded to get current template stats.
$.get( preview, function( renderedTemplate ) {
var headings, h1, h2, $rendered;
// The rendered page content.
$rendered = $( renderedTemplate );
// H1's that appear in rendered content.
h1 = $rendered.find( 'h1' );
// HS's that appear in rendered content.
h2 = $rendered.find( 'h2' );
// The rendered content stats.
renderedContent = {
h1Count : h1.length - report.rawstatistics.h1Count,
h1text : _.filter( api.Headings.getHeadingText( h1 ), function( obj ){
return ! _.findWhere( report.rawstatistics.h1text, obj );
}),
h2Count : h2.length - report.rawstatistics.h2Count,
h2text : _.filter( api.Headings.getHeadingText( h2 ), function( obj ){
return ! _.findWhere( report.rawstatistics.h2text, obj );
}),
};
// Add the rendered stats to our report for use later.
_.extend( report, { rendered : renderedContent } );
// Trigger the SEO report to rebuild in the template after initial stats are created.
self.triggerAnalysis();
}, 'html' );
}
},
/**
* Strips out unwanted html.
*
* This is helpful in removing the remaining traces of HTML
* that is sometimes leftover to form our clean text output and
* run our text analysis on.
*
* @since 1.3.1
*
* @returns {string} The content with any remaining html removed.
*/
stripper : function( html ) {
var tmp;
tmp = document.implementation.createHTMLDocument( 'New' ).body;
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || " ";
},
/**
* Fire an event that will force, analysis to run.
*
* @since 1.6.0
*/
triggerAnalysis: function() {
self.element.trigger( 'bgseo-analysis', [ self.ui.getContent() ] );
}
};
self = api.Editor;
})( jQuery );
( function ( $ ) {
'use strict';
var self, api;
api = BOLDGRID.SEO;
/**
* BoldGrid Gutenberg Analysis.
*
* This is responsible for generating the actual reports
* displayed within the BoldGrid SEO Dashboard when the user
* is on a page or a post.
*
* @since 1.3.1
*/
api.Gutenberg = {
/**
* Selector to find editor id.
*
* This is only used to trigger events. No dom content queries in Gutenberg context.
*
* @since 1.6.0
*
* @type {String}
*/
selector : '#editor',
/**
* Selector to find preview button.
*
* @since 1.6.0
*
* @type {String}
*/
previewSelector : '.editor-post-preview',
/**
* Initialize Content.
*
* @since 1.6.0
*/
setup : function () {
$( api.Editor.triggerAnalysis );
self._setupEditorChange();
},
/**
* Are we currently on a new post?
*
* @since 1.6.0
*
* @return {boolean} Is this a post-new.php?
*/
isNewPost : function() {
return wp.data.select( 'core/editor' ).isCleanNewPost();
},
/**
* Gets the content from the editor for analysis.
*
* @since 1.6.0
*
* @returns {Object} content Contains content in raw and text formats.
*/
getContent : function() {
var content = self.getRawText();
// Stores raw and stripped down versions of the content for analysis.
content = {
'raw': content,
'text': api.Editor.stripper( content.toLowerCase() ),
};
return content;
},
/**
* Get the raw text from the editor.
*
* @since 1.6.0
*
* @return {string} Editor content.
*/
getRawText : function () {
return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'content' );
},
/**
* Listens for changes made in the text editor mode.
*
* @since 1.6.0
*
* @returns {string} text The new content to perform analysis on.
*/
_setupEditorChange: function() {
var latestContent = '';
wp.data.subscribe( _.debounce( function () {
// Make sure content is different before running analysis.
if ( self.getRawText() !== latestContent ) {
api.Wordcount.update();
api.Editor.triggerAnalysis();
latestContent = self.getRawText();
}
}, 1000 ) );
}
};
self = api.Gutenberg;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Headings.
*
* This is responsible for the SEO Headings Grading.
*
* @since 1.3.1
*/
api.Headings = {
/**
* Initialize SEO Headings Analysis.
*
* @since 1.3.1
*/
init : function () {
$( document ).ready( self.onReady );
},
/**
* Sets up event listeners and selector cache in settings on document ready.
*
* @since 1.3.1
*/
onReady : function() {
self.getSettings();
self._checkbox();
},
/**
* Cache selectors
*
* @since 1.3.1
*/
getSettings : function() {
self.settings = {
displayTitle : $( '[name="boldgrid-display-post-title"]' ).last(),
};
},
/**
* Sets up event listener for Display page title checkbox.
*
* Listens for checkbox changes and updates the status message.
*
* @since 1.3.1
*/
_checkbox : function() {
// Listen for changes to input value.
self.settings.displayTitle.on( 'change', _.debounce( function() {
$( this ).trigger( 'bgseo-analysis', [ api.Editor.ui.getContent() ] );
}, 1000 ) );
},
/**
* Initialize BoldGrid SEO Headings Analysis.
*
* @since 1.3.1
*/
score : function( count ) {
var msg;
// Set default message for h1 headings score.
msg = {
status : 'green',
msg : _bgseoContentAnalysis.headings.h1.good,
};
// If we have more than one H1 tag rendered.
if ( count > 1 ) {
msg = {
status : 'red',
msg : _bgseoContentAnalysis.headings.h1.badMultiple,
};
}
// If we have more than one H1 tag rendered.
if ( count > 1 && self.settings.displayTitle.is( ':checked' ) ) {
msg = {
status : 'red',
msg : _bgseoContentAnalysis.headings.h1.badBoldgridTheme,
};
}
// If no H1 tag is present.
if ( 0 === count ) {
msg = {
status : 'red',
msg : _bgseoContentAnalysis.headings.h1.badEmpty,
};
}
return msg;
},
/**
* Gets count of how many times keywords appear in headings.
*
* @since 1.3.1
*
* @param {Object} headings The headings count object to check against.
*
* @returns {Number} How many times the keyword appears in the headings.
*/
keywords : function( headings ) {
var found = { length : 0 },
keyword = api.Keywords.getKeyword();
// If not passing in headings, attempt to find default headings.
if ( _.isUndefined( headings ) ) {
headings = { count : self.getRealHeadingCount() };
}
// Don't process report item if headings are empty.
if ( _.isEmpty( headings ) ) return;
// Get the count.
_( headings.count ).each( function( value, key ) {
var text = value.text;
// Add to the found object for total occurences found for keyword in headings.
_( text ).each( function( item ) {
found.length = Number( found.length ) + Number( item.heading.occurences( keyword ) * item.count );
});
});
return found.length;
},
/**
* Get the text inside of headings.
*
* @since 1.3.1
*
* @param {Object} selectors jQuery wrapped selector object.
*
* @returns {Array} headingText Contains each selectors' text.
*/
getHeadingText : function( selectors ) {
var headingText = {};
headingText = _.countBy( selectors, function( value, key ) {
return $.trim( $( value ).text().toLowerCase() );
});
headingText = _.map( headingText, function( value, key ) {
return _( headingText ).has({ heading : key, count : value }) ? false : {
heading : key,
count : value,
};
});
return headingText;
},
/**
* Gets the actual headings count based on the rendered page and the content.
*
* This only needs to be fired if the rendered report
* data is available for analysis. The calculations take
* into account the template in use for the page/post and
* are stored earlier on in the load process when the user
* first enters the editor.
*
* @since 1.3.1
*
* @returns {Object} headings Count of H1, H2, and H3 tags used for page/post.
*/
getRealHeadingCount : function() {
var headings = {};
// Only get this score if rendered content score has been provided.
if ( ! _.isUndefined( report.rendered ) ) {
// Stores the heading coutns for h1-h3 for later analysis.
headings = {
count: {
h1 : {
length : report.rendered.h1Count + report.rawstatistics.h1Count,
text : _( report.rendered.h1text ).union( report.rawstatistics.h1text ),
},
h2 : {
length : report.rendered.h2Count + report.rawstatistics.h2Count,
text : _( report.rendered.h2text ).union( report.rawstatistics.h2text ),
},
},
};
// Add the score of H1 presence to the headings object.
_( headings ).extend({
lengthScore : self.score( headings.count.h1.length ),
});
} else {
headings = self.getContentHeadings();
}
return headings;
},
/**
* Get the headings that exist in the raw content.
*
* This will get the content and check if any h1s or
* h2s exist in the raw markup. If they are present, it will
* update the report with new count information and text.
*
* @since 1.3.1
*
* @returns {Object} headings Counts of h1 and h2 tags in content.
*/
getContentHeadings : function() {
var headings, h1s, h2s, content;
// Set default counts.
headings = {
count: {
h1 : {
length : 0,
text : {},
},
h2 : {
length : 0,
text : {},
},
},
};
content = api.Editor.ui.getContent();
content = $( '<div>' + content.raw + '</div>' );
h1s = content.find( 'h1' );
h2s = content.find( 'h2' );
// If no h1s or h2s are found return the defaults.
if ( ! h1s.length && ! h2s.length ) return headings;
headings = {
count: {
h1 : {
length : h1s.length,
text : self.getHeadingText( h1s ),
},
h2 : {
length : h2s.length,
text : self.getHeadingText( h2s ),
},
},
};
return headings;
},
};
self = api.Headings;
})( jQuery );
( function( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Keywords.
*
* This is responsible for the SEO Keywords Analysis and Scoring.
*
* @since 1.3.1
*/
api.Keywords = {
/**
* Initialize BoldGrid SEO Keyword Analysis.
*
* @since 1.3.1
*/
init : function () {
$( document ).ready( self.onReady );
},
/**
* Sets up event listeners and selector cache in settings on document ready.
*
* @since 1.3.1
*/
onReady : function() {
self.getSettings();
self._keywords();
self.setPlaceholder();
},
/**
* Cache selectors
*
* @since 1.3.1
*/
getSettings : function() {
self.settings = {
keyword : $( '#bgseo-custom-keyword' ),
content : api.Editor.element,
};
},
/**
* Sets up event listener for changes made to the custom keyword input.
*
* Listens for changes being made to the custom keyword input, and then
* triggers the reporter to be updated with new status/score.
*
* @since 1.3.1
*/
_keywords: function() {
self.settings.keyword.on( 'input propertychange paste', _.debounce( function() {
var msg = {},
length = self.settings.keyword.val().length;
msg = {
keywords : {
title : {
length : api.Title.keywords(),
lengthScore : 0,
},
description : {
length : api.Description.keywords(),
lengthScore : 0,
},
keyword : self.getCustomKeyword(),
},
};
self.settings.keyword.trigger( 'bgseo-analysis', [msg] );
}, 1000 ) );
},
setPlaceholder : function( keyword ) {
self.settings.keyword.attr( 'placeholder', keyword );
},
/**
* Gets the count of the keywords in the content passed in.
*
* @since 1.3.1
*
* @param {string} content The content to count keyword frequency in.
* @param {string} keyword The keyword/phrase to search for.
*
* @returns {Number} keywordCount Represents how many times a keyword appears.
*/
keywordCount: function( content, keyword ) {
var keywordCount;
keywordCount = content.split( keyword ).length - 1;
return keywordCount;
},
/**
* Gets the count of words in the keyword phrase section.
*
* @since 1.3.1
*
* @param {string} keywordPhrase The content to count words in.
*
* @returns {Number} Number of words in keywordPhrase.
*/
phraseLength: function( keywordPhrase ) {
// Check for empty strings.
if ( keywordPhrase.length === 0 ) {
return 0;
}
// Excludes start and end white-space.
keywordPhrase = keywordPhrase.replace( /(^\s*)|(\s*$)/gi, '' );
// 2 or more space to 1.
keywordPhrase = keywordPhrase.replace( /[ ]{2,}/gi, ' ' );
// Exclude newline with a start spacing.
keywordPhrase = keywordPhrase.replace( /\n /, '\n' );
return keywordPhrase.split( ' ' ).length;
},
/**
* Calculates keyword density for content and keyword passed in.
*
* @since 1.3.1
*
* @param {string} content The content to calculate density for.
*
* @returns {Number} result Calculated density of keyword in content passed.
*/
keywordDensity : function( content ) {
var result, keywordCount, wordCount, keyword;
keyword = self.getKeyword();
// Return 0 without calculation if no custom keyword is found.
if ( _.isUndefined( keyword ) ) return 0;
// Normalize.
keyword = keyword.toLowerCase();
keywordCount = self.keywordCount( content, keyword );
wordCount = api.Wordcount.count;
// Get the density.
result = ( ( keywordCount / wordCount ) * 100 );
// Round it off.
result = Math.round( result * 10 ) / 10;
return result;
},
/**
* Normalizes the stop words to match the words returned by the WP
* WordCount.
*
* @since 1.3.2
*
* @param {string} str Word to normalize.
*
* @returns {string} Normalized word.
*/
normalizeWords: function( str ) {
return str.replace( '\'', '' );
},
/**
* Trims values of whitespace.
*
* @since 1.3.2
*
* @param {string} str Word to trim.
*
* @returns {string} Trimmed word.
*/
trim: function( str ) {
return str.trim();
},
/**
* Gets the recommended keywords from content.
*
* This is what gets suggested to a user that their content is about this
* keyword if they do not enter in a custom target keyword or phrase.
*
* @since 1.3.1
*
* @param {Array} words The words to search through.
* @param {Number} n How many keywords to return back.
*
* @returns {Array} result An array of n* most frequent keywords.
*/
recommendedKeywords: function( words, n ) {
var stopWords = _bgseoContentAnalysis.stopWords,
positions = {},
wordCounts = [],
result;
// Abort if no words are passed in.
if ( _.isEmpty( words ) ) return;
// Create array from string passed, and trim array values.
stopWords = stopWords.split( ',' ).map( self.trim );
// Normalize the stopWords to watch WordPress words.
stopWords = stopWords.map( self.normalizeWords );
for ( var i = 0; i < words.length; i++ ) {
var word = $.trim( words[i] ).toLowerCase();
// Make sure word isn't in our stop words and is longer than 3 characters.
if ( ! word || word.length < 3 || stopWords.indexOf( word ) > -1 ) {
continue;
}
if ( _.isUndefined( positions[ word ] ) ) {
positions[ word ] = wordCounts.length;
wordCounts.push( [ word, 1 ] );
} else {
wordCounts[ positions[ word ] ][1]++;
}
}
// Put most frequent words at the beginning.
wordCounts.sort( function ( a, b ) {
return b[1] - a[1];
});
// Return the first n items
result = wordCounts.slice( 0, n );
return result;
},
/**
* Retrieves User's Custom SEO Keyword.
*
* If the user has entered in a custom keyword to run evaluation on,
* then we will retrieve this value instead of the automatically
* generated keyword recommendation.
*
* @since 1.3.1
*
* @returns {string} Trimmed output of user supplied custom keyword.
*/
getCustomKeyword : function() {
return $.trim( self.settings.keyword.val() ).toLowerCase();
},
/**
* Used to get the keyword for the report.
*
* Checks if a custom keyword has been set by the user, and
* if it hasn't it will use the autogenerated keyword that was
* determined based on the content.
*
* @since 1.3.1
*
* @returns {string} customKeyword Contains the customKeyword to add to report.
*/
getKeyword : function() {
var customKeyword,
content = api.Editor.ui.getRawText();
if ( self.getCustomKeyword().length ) {
customKeyword = self.getCustomKeyword();
} else if ( ! _.isUndefined( report.textstatistics.recommendedKeywords ) &&
! _.isUndefined( report.textstatistics.recommendedKeywords[0] ) ) {
// Set customKeyword to recommended keyword search.
customKeyword = report.textstatistics.recommendedKeywords[0][0];
} else if ( _.isEmpty( $.trim( content.text ) ) ) {
customKeyword = undefined;
} else {
self.recommendedKeywords( api.Words.words( content.raw ), 1 );
}
return customKeyword;
},
/**
* Used to get the recommended keyword count.
*
* Gets the percentages provided for minimum and maximum keyword
* densities from the configs. The number is based on the amount of words
* that make up the current page/post.
*
* @since 1.3.1
*
* @returns {Object} count Range for count of keywords based on content length.
*/
getRecommendedCount : function( markup ) {
var count;
if ( _.isUndefined( markup ) ) {
markup = api.Words.words( api.Editor.ui.getRawText() );
}
count = _.modifyObject( _bgseoContentAnalysis.keywords.recommendedCount, function( item ) {
var numb = Number( ( item / 100 ) * api.Words.words( markup ).length ).rounded( 0 );
// Set minimum recommended count to at least once.
return numb > 0 ? numb : 1;
});
return count;
},
/**
* Used to get the keyword for the report.
*
* Checks if a custom keyword has been set by the user, and
* if it hasn't it will use the autogenerated keyword that was
* determined based on the content.
*
* @since 1.3.1
*
* @returns {Object} msg Contains the scoring for each keyword related item.
*/
score : function() {
var msg = {};
msg = {
title : self.titleScore(),
description : self.descriptionScore(),
};
return msg;
},
/**
* Used to get the keyword usage scoring description for the title.
*
* Checks the count provided for the number of times the keyword was
* used in the SEO Title.
*
* @since 1.3.1
*
* @param {Number} count The number of times keyword is used in the title.
*
* @returns {Object} msg Contains the status indicator color and message for report.
*/
titleScore : function( count ) {
var msg;
// Default status and message.
msg = {
status: 'green',
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.good,
};
// Keyword not used in title.
if ( 0 === count ) {
msg = {
status: 'red',
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.bad,
};
}
// Keyword used in title at least once.
if ( count > 1 ) {
msg = {
status: 'yellow',
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.ok,
};
}
return msg;
},
/**
* Used to get the keyword usage scoring description for the description.
*
* Checks the count provided for the number of times the keyword was
* used in the SEO Description field.
*
* @since 1.3.1
*
* @param {Number} count The number of times keyword is used in the description.
*
* @returns {Object} msg Contains the status indicator color and message for report.
*/
descriptionScore : function( count ) {
var msg;
// Default status and message.
msg = {
status: 'green',
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.good,
};
// If not used at all in description.
if ( 0 === count ) {
msg = {
status: 'red',
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.bad,
};
}
// If used at least one time in description.
if ( count > 1 ) {
msg = {
status: 'yellow',
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.ok,
};
}
return msg;
},
/**
* Gets keyword score for content.
*
* Used to get the status and message for the content's keyword usage.
*
* @since 1.3.1
*
* @param {Number} count The number of times keyword is used in the content.
*
* @returns {Object} msg Contains the status indicator color and message for report.
*/
contentScore : function( count ) {
var msg, range, description;
// Get the keyword range based on the content length.
range = self.getRecommendedCount();
// Keyword not used at all in content.
if ( 0 === count ) {
msg = {
status: 'red',
msg : _bgseoContentAnalysis.content.keywordUsage.bad,
};
}
// Keyword used within the range calculated based on content length.
if ( count.isBetween( range.min - 1, range.max + 1 ) ) {
description = 1 === range.min ?
_bgseoContentAnalysis.content.keywordUsage.goodSingular :
_bgseoContentAnalysis.content.keywordUsage.good.printf( range.min );
msg = {
status: 'green',
msg : description,
};
}
// Keyword used less than the minimum of the range specified, but not 0 times.
if ( count < range.min && count !== 0 ) {
description = 1 === range.min ?
_bgseoContentAnalysis.content.keywordUsage.okShortSingular :
_bgseoContentAnalysis.content.keywordUsage.okShort.printf( range.min );
msg = {
status: 'yellow',
msg : description,
};
}
// Key word used more than 3 times in the content.
if ( count > range.max ) {
description = 1 === range.min ?
_bgseoContentAnalysis.content.keywordUsage.okLongSingular :
_bgseoContentAnalysis.content.keywordUsage.okLong.printf( range.min );
msg = {
status: 'red',
msg : description,
};
}
return msg;
},
/**
* Gets keyword score for headings.
*
* Used to get the status and message for the heading's keyword usage.
*
* @since 1.3.1
*
* @param {Number} count The number of times keyword is used in the headings.
*
* @returns {Object} msg Contains the status indicator color and message for report.
*/
headingScore : function( count ) {
var msg;
// Default message.
msg = {
status: 'green',
msg : _bgseoContentAnalysis.headings.keywordUsage.good,
};
// Keyword not used at all in content.
if ( 0 === count ) {
msg = {
status: 'red',
msg : _bgseoContentAnalysis.headings.keywordUsage.bad,
};
}
// Key word used more than 3 times in the content.
if ( count > 3 ) {
msg = {
status: 'yellow',
msg : _bgseoContentAnalysis.headings.keywordUsage.ok,
};
}
return msg;
},
/**
* Used to get the scoring description for the keyword phrase.
*
* Returns the status message based on how many words are in the phrase.
*
* @since 1.3.1
*
* @param {Number} count WordCount for phrase.
*
* @returns {Object} msg Contains the status indicator color and message for report.
*/
keywordPhraseScore : function( count ) {
var msg;
// Default status and message.
msg = {
status: 'green',
msg : _bgseoContentAnalysis.keywords.keywordPhrase.good,
};
// Keyword used in title at least once.
if ( 1 === count ) {
msg = {
status: 'yellow',
msg : _bgseoContentAnalysis.keywords.keywordPhrase.ok,
};
}
// Keyword not used in title.
if ( 0 === count ) {
msg = {
status: 'red',
msg : _bgseoContentAnalysis.keywords.keywordPhrase.bad,
};
}
return msg;
},
};
self = api.Keywords;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Readability.
*
* This is responsible for the SEO Reading Score and Grading.
*
* @since 1.3.1
*/
api.Readability = {
/**
* Gets the Flesch Kincaid Grade based on the content.
*
* @since 1.3.1
*
* @param {String} content The content to run the analysis on.
*
* @returns {Number} result A number representing the grade of the content.
*/
gradeLevel : function( content ) {
var grade, result = {};
grade = textstatistics( content ).fleschKincaidReadingEase();
result = self.gradeAnalysis( grade );
return result;
},
/**
* Returns information about the grade for display.
*
* This will give back human readable explanations of the grading, so
* the user can make changes based on their score accurately.
*
* @since 1.3.1
*
* @param {Number} grade The grade to evalute and return response for.
*
* @returns {Object} description Contains status, explanation and associated grade level.
*/
gradeAnalysis : function( grade ) {
var scoreTranslated, description = {};
// Grade is higher than 90.
if ( grade > 90 ) {
description = {
'score' : grade,
'gradeLevel' : '5th grade',
'explanation': 'Very easy to read. Easily understood by an average 11-year-old student.',
lengthScore : {
'status' : 'green',
'msg' : _bgseoContentAnalysis.readingEase.goodHigh,
},
};
}
// Grade is 80-90.
if ( grade.isBetween( 79, 91 ) ) {
description = {
'score' : grade,
'gradeLevel' : '6th grade',
'explanation': 'Easy to read. Conversational English for consumers.',
lengthScore : {
'status' : 'green',
'msg' : _bgseoContentAnalysis.readingEase.goodMedHigh,
},
};
}
// Grade is 70-90.
if ( grade.isBetween( 69, 81 ) ) {
description = {
'score' : grade,
'gradeLevel' : '7th grade',
'explanation': 'Fairly easy to read.',
lengthScore : {
'status' : 'green',
'msg' : _bgseoContentAnalysis.readingEase.goodMedLow,
}
};
}
// Grade is 60-70.
if ( grade.isBetween( 59, 71 ) ) {
description = {
'score' : grade,
'gradeLevel' : '8th & 9th',
'explanation': 'Plain English. Easily understood by 13- to 15-year-old students.',
lengthScore : {
'status' : 'green',
'msg' : _bgseoContentAnalysis.readingEase.goodLow,
},
};
}
// Grade is 50-60.
if ( grade.isBetween( 49, 61 ) ) {
description = {
'score' : grade,
'gradeLevel' : '10th to 12th',
'explanation': 'Fairly difficult to read.',
lengthScore : {
'status' : 'yellow',
'msg' : _bgseoContentAnalysis.readingEase.ok,
},
};
}
// Grade is 30-50.
if ( grade.isBetween( 29, 51 ) ) {
description = {
'score' : grade,
'gradeLevel' : 'College Student',
'explanation': 'Difficult to read.',
lengthScore : {
'status' : 'red',
'msg' : _bgseoContentAnalysis.readingEase.badHigh,
},
};
}
// Grade is less than 30.
if ( grade < 30 ) {
description = {
'score' : grade,
'gradeLevel' : 'College Graduate',
'explanation': 'Difficult to read.',
lengthScore : {
'status' : 'red',
'msg' : _bgseoContentAnalysis.readingEase.badLow,
},
};
}
// Add translated score string to message.
scoreTranslated = _bgseoContentAnalysis.readingEase.score.printf( grade ) + ' ';
description.lengthScore.msg = description.lengthScore.msg.replace( /^/, scoreTranslated );
return description;
},
};
self = api.Readability;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid Editor Content Analysis.
*
* This is responsible for generating the actual reports
* displayed within the BoldGrid SEO Dashboard when the user
* is on a page or a post.
*
* @since 1.3.1
*/
api.Report = {
/**
* Initialize Content.
*
* @since 1.3.1
*/
init : function () {
$( document ).ready( self.onReady );
},
/**
* Sets up event listeners and selector cache in settings on document ready.
*
* @since 1.3.1
*/
onReady : function() {
self.getSettings();
self.generateReport();
},
/**
* Cache selectors
*
* @since 1.3.1
*/
getSettings : function() {
self.settings = {
title : $( '#boldgrid-seo-field-meta_title' ),
description : $( '#boldgrid-seo-field-meta_description' )
};
},
/**
* Generate the Report based on analysis done.
*
* This will generate a report object and then trigger the
* reporter event, so that the model is updated and changes
* are reflected live for the user in their SEO Dashboard.
*
* @since 1.3.1
*/
generateReport : function() {
if ( _.isUndefined( self.settings ) ) return;
$( document ).on( 'bgseo-analysis', function( e, eventInfo ) {
var words, titleLength, descriptionLength;
// Get length of title field.
titleLength = self.settings.title.val().length;
// Get length of description field.
descriptionLength = self.settings.description.val().length;
if ( eventInfo.words ) {
_( report.textstatistics ).extend({
recommendedKeywords : api.Keywords.recommendedKeywords( eventInfo.words, 1 ),
customKeyword : api.Keywords.getKeyword(),
});
}
// Listen for event changes being triggered.
if ( eventInfo ) {
// Listen for changes to raw HTML in editor.
if ( eventInfo.raw ) {
// Prepend eventInfo.raw to an empty div so that the find commands work correctly.
var $raws = $( '<div></div>' ).prepend( eventInfo.raw ),
h1 = $raws.find( 'h1' ),
h2 = $raws.find( 'h2' ),
headings = {};
headings = {
h1Count : h1.length,
h1text : api.Headings.getHeadingText( h1 ),
h2Count : h2.length,
h2text : api.Headings.getHeadingText( h2 ),
imageCount: $raws.find( 'img' ).length,
};
// Set the heading counts and image count found in new content update.
_( report.rawstatistics ).extend( headings );
}
if ( eventInfo.keywords ) {
_( report.bgseo_keywords ).extend({
keywordPhrase: {
length : api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ),
lengthScore : api.Keywords.keywordPhraseScore( api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ) ),
},
keywordTitle : {
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
},
keywordDescription : {
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
},
keywordContent : {
lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.Editor.ui.getContent().text ) ),
},
keywordHeadings : {
length : api.Headings.keywords( api.Headings.getRealHeadingCount() ),
lengthScore : api.Keywords.headingScore( api.Headings.keywords( api.Headings.getRealHeadingCount() ) ),
},
customKeyword : eventInfo.keywords.keyword,
});
}
// Listen for changes to the actual text entered by user.
if ( eventInfo.text ) {
var kw, headingCount = api.Headings.getRealHeadingCount(),
content = eventInfo.text,
raw = api.Editor.ui.getRawText();
// Get length of title field.
titleLength = self.settings.title.val().length;
// Get length of description field.
descriptionLength = self.settings.description.val().length;
// Set the placeholder attribute once the keyword has been obtained.
kw = api.Keywords.recommendedKeywords( raw, 1 );
if ( ! _.isUndefined( kw ) && ! _.isUndefined( kw[0] ) ) api.Keywords.setPlaceholder( kw[0][0] );
// Set the default report items.
_( report ).extend({
bgseo_meta : {
title : {
length : titleLength,
lengthScore : api.Title.titleScore( titleLength ),
},
description : {
length : descriptionLength,
lengthScore : api.Description.descriptionScore( descriptionLength ),
keywordUsage : api.Description.keywords(),
},
titleKeywordUsage : {
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
},
descKeywordUsage : {
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
},
sectionScore : {},
sectionStatus : {},
},
bgseo_visibility : {
robotIndex : {
lengthScore: api.Robots.indexScore(),
},
robotFollow : {
lengthScore: api.Robots.followScore(),
},
sectionScore : {},
sectionStatus : {},
},
bgseo_keywords : {
keywordPhrase: {
length : api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ),
lengthScore : api.Keywords.keywordPhraseScore( api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ) ),
},
keywordTitle : {
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
},
keywordDescription : {
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
},
keywordContent : {
lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.Editor.ui.getContent().text ) ),
},
keywordHeadings : {
length : api.Headings.keywords( headingCount ),
lengthScore : api.Keywords.headingScore( api.Headings.keywords( headingCount ) ),
},
image : {
length : report.rawstatistics.imageCount,
lengthScore : api.ContentAnalysis.seoImageLengthScore( report.rawstatistics.imageCount ),
},
headings : headingCount,
wordCount : {
length : api.Wordcount.count,
lengthScore : api.ContentAnalysis.seoContentLengthScore( api.Wordcount.count ),
},
sectionScore: {},
sectionStatus: {},
},
textstatistics : {
recommendedKeywords : kw,
recommendedCount : api.Keywords.getRecommendedCount( raw ),
keywordDensity : api.Keywords.keywordDensity( content, api.Keywords.getKeyword() ),
},
});
}
// Listen to changes to the SEO Title and update report.
if ( eventInfo.titleLength ) {
_( report.bgseo_meta.title ).extend({
length : eventInfo.titleLength,
lengthScore : api.Title.titleScore( eventInfo.titleLength ),
});
_( report.bgseo_meta.titleKeywordUsage ).extend({
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
});
_( report.bgseo_keywords.keywordTitle ).extend({
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
});
api.Editor.triggerAnalysis();
}
// Listen to changes to the SEO Description and update report.
if ( eventInfo.descLength ) {
_( report.bgseo_meta.description ).extend({
length : eventInfo.descLength,
lengthScore: api.Description.descriptionScore( eventInfo.descLength ),
});
_( report.bgseo_meta.descKeywordUsage ).extend({
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
});
_( report.bgseo_keywords.keywordDescription ).extend({
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
});
api.Editor.triggerAnalysis();
}
// Listen for changes to noindex/index and update report.
if ( eventInfo.robotIndex ) {
_( report.bgseo_visibility.robotIndex ).extend({
lengthScore : eventInfo.robotIndex,
});
api.Editor.triggerAnalysis();
}
// Listen for changes to nofollow/follow and update report.
if ( eventInfo.robotFollow ) {
_( report.bgseo_visibility.robotFollow ).extend({
lengthScore : eventInfo.robotFollow,
});
api.Editor.triggerAnalysis();
}
}
// Send the final analysis to display the report.
api.Editor.element.trigger( 'bgseo-report', [ report ] );
});
},
/**
* Get's the current report that's generated for output.
*
* This is used for debugging, and to also obtain the current report in
* other classes to perform scoring, analysis, and status indicator updates.
*
* @since 1.3.1
*
* @returns {Object} report The report data that's currently displayed.
*/
get : function( key ) {
var data = {};
if ( _.isUndefined( key ) ) {
data = report;
} else {
data = _.pickDeep( report, key );
}
return data;
},
};
self = api.Report;
})( jQuery );
var BOLDGRID = BOLDGRID || {};
BOLDGRID.SEO = BOLDGRID.SEO || {};
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Robots.
*
* This is responsible for the noindex and nofollow checkbox
* listeners, and returning status/scores for each.
*
* @since 1.3.1
*/
api.Robots = {
/**
* Initialize BoldGrid SEO Robots.
*
* @since 1.3.1
*/
init : function () {
$( document ).ready( self.onReady );
},
/**
* Sets up event listeners and selector cache in settings on document ready.
*
* @since 1.3.1
*/
onReady : function() {
self.getSettings();
self._index();
self._follow();
},
/**
* Cache selectors
*
* @since 1.3.1
*/
getSettings : function() {
self.settings = {
indexInput : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index]' ),
noIndex : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index][value="noindex"]' ),
followInput : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow]' ),
noFollow : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow][value="nofollow"]' ),
};
},
/**
* Sets up event listener for index/noindex radios.
*
* Listens for changes being made on the radios, and then
* triggers the reporter to be updated with new status/score.
*
* @since 1.3.1
*/
_index : function() {
self.settings.indexInput.on( 'change', function() {
$( this ).trigger( 'bgseo-analysis', [{ 'robotIndex': self.indexScore() }] );
});
},
/**
* Gets score of index/noindex status.
*
* Checks if index/noindex is checked and returns appropriate
* status message and indicator.
*
* @since 1.3.1
* @returns {Object} Contains status indicator color and message to update.
*/
indexScore : function() {
var msg;
// Index radio is selected.
msg = {
status: 'green',
msg: _bgseoContentAnalysis.noIndex.good,
};
// Noindex radio is selected.
if ( self.settings.noIndex.is( ':checked' ) ) {
msg = {
status: 'red',
msg: _bgseoContentAnalysis.noIndex.bad,
};
}
return msg;
},
/**
* Sets up event listener for follow/nofollow radios.
*
* Listens for changes being made on the radios, and then
* triggers the reporter to be updated with new status/score.
*
* @since 1.3.1
*/
_follow : function() {
// Listen for changes to input value.
self.settings.followInput.on( 'change', function() {
$( this ).trigger( 'bgseo-analysis', [{ 'robotFollow': self.followScore() }] );
});
},
/**
* Gets score of follow/nofollow status.
*
* Checks if follow or nofollow is checked, and returns appropriate
* status message and indicator.
*
* @since 1.3.1
* @returns {Object} Contains status indicator color and message to update.
*/
followScore : function() {
var msg = {
status: 'green',
msg: _bgseoContentAnalysis.noFollow.good,
};
if ( self.settings.noFollow.is( ':checked' ) ) {
msg = {
status: 'yellow',
msg: _bgseoContentAnalysis.noFollow.bad,
};
}
return msg;
},
};
self = api.Robots;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Sections.
*
* This is responsible for section related statuses and modifications.
*
* @since 1.3.1
*/
api.Sections = {
/**
* Gets the status for a section.
*
* This will get the status based on the scores received for each
* section and return the status color as the report is updated.
*
* @since 1.3.1
*
* @param {Object} sectionScores The scores for the section.
*
* @returns {string} status The status color to assign to the section.
*/
status : function( sectionScores ) {
// Default status is set to green.
var status = 'green';
// Check if we have any red or yellow statuses and update as needed.
if ( sectionScores.red > 0 ) {
status = 'red';
} else if ( sectionScores.yellow > 0 ) {
status = 'yellow';
}
return status;
},
/**
* Gets the score and status of a section.
*
* This is responsible for getting the count of statuses that
* are set for each item in the report for a section. It will
* return the data that is added to the report..
*
* @since 1.3.1
*
* @param {Object} section The section to get a score for.
*
* @returns {Object} data Contains the section status scores and section status.
*/
score : function( section ) {
var sectionScores, score, data;
// Set default counters for each status.
sectionScores = { red: 0, green : 0, yellow : 0 };
// Get the count of scores in object by status.
score = _( section ).countBy( function( items ) {
return ! _.isUndefined( items.lengthScore ) && 'sectionScore' !== _.property( 'sectionScore' )( section ) ? items.lengthScore.status : '';
});
// Update the object with the new count.
_( score ).each( function( value, key ) {
if ( _.has( sectionScores , key ) ) {
sectionScores[key] = value;
}
});
// Update the section's score and status.
data = {
sectionScore : sectionScores,
sectionStatus: self.status( sectionScores ),
};
return data;
},
removeStatus : function( selector ) {
selector.removeClass( 'red yellow green' );
},
navHighlight : function( report ) {
_.each( butterbean.models.sections, function( item ) {
var selector,
manager = item.get( 'manager' ),
name = item.get( 'name' );
selector = $( '[href="#butterbean-' + manager + '-section-' + name + '"]' ).closest( 'li' );
self.removeStatus( selector );
selector.addClass( report[name].sectionStatus );
});
},
overviewStatus : function( report ) {
var selector = $( "#butterbean-ui-boldgrid_seo.postbox > h2 > span:contains('Easy SEO')" );
self.removeStatus( selector );
selector.addClass( 'overview-status ' + report.bgseo_keywords.overview.status );
}
};
self = api.Sections;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Title.
*
* This is responsible for the SEO Title Grading.
*
* @since 1.3.1
*/
api.Title = {
/**
* Initialize SEO Title Analysis.
*
* @since 1.3.1
*/
init : function () {
$( document ).ready( self.onReady );
},
/**
* Sets up event listeners and selector cache in settings on document ready.
*
* @since 1.3.1
*/
onReady : function() {
self.getSettings();
self._title();
},
/**
* Cache selectors
*
* @since 1.3.1
*/
getSettings : function() {
self.settings = {
title : $( '#boldgrid-seo-field-meta_title' ),
};
},
/**
* Gets the SEO Title.
*
* @since 1.3.1
*
* @returns {Object} title Contains wrapped set with BoldGrid SEO Title.
*/
getTitle : function() {
return self.settings.title;
},
/**
* Sets up event listener for changes made to the SEO Title.
*
* Listens for changes being made to the SEO Title, and then
* triggers the reporter to be updated with new status/score.
*
* @since 1.3.1
*/
_title: function() {
// Listen for changes to input value.
self.settings.title.on( 'input propertychange paste', _.debounce( function() {
self.settings.title.trigger( 'bgseo-analysis', [{ titleLength : self.settings.title.val().length }] );
}, 1000 ) );
},
/**
* Gets score of the SEO Title.
*
* Checks the length provided and returns a score for the SEO
* title. This score is based on character count.
*
* @since 1.3.1
*
* @param {Number} titleLength The length of the title to generate score for.
*
* @returns {Object} msg Contains status indicator color and message to update.
*/
titleScore: function( titleLength ) {
var msg = {}, title;
title = _bgseoContentAnalysis.seoTitle.length;
// No title entered.
if ( titleLength === 0 ) {
msg = {
status: 'red',
msg: title.badEmpty,
};
}
// Title is 1-30 characters.
if ( titleLength.isBetween( 0, title.okScore + 1 ) ) {
msg = {
status: 'yellow',
msg: title.ok,
};
}
// Title is 30-70 characters.
if ( titleLength.isBetween( title.okScore - 1, title.goodScore + 1 ) ) {
msg = {
status: 'green',
msg: title.good,
};
}
// Title is grater than 70 characters.
if ( titleLength > title.goodScore ) {
msg = {
status: 'red',
msg: title.badLong,
};
}
return msg;
},
/**
* Get count of keywords used in the title.
*
* This checks the title for keyword frequency.
*
* @since 1.3.1
*
* @param {String} text (Optional) The text to search for keyword in.
* @param {String} keyword (Optional) The keyword to search for.
*
* @returns {Number} Count of times keyword appears in text.
*/
keywords : function( text, keyword ) {
if ( 0 === arguments.length ) {
keyword = api.Keywords.getKeyword();
text = self.getTitle().val();
} else if ( 1 === arguments.length ) {
keyword = api.Keywords.getKeyword();
}
// Normalize user input.
text = text.toLowerCase();
return text.occurences( keyword );
},
};
self = api.Title;
})( jQuery );
( function ( $ ) {
'use strict';
var self, report, api;
api = BOLDGRID.SEO;
report = api.report;
/**
* BoldGrid SEO Tooltips.
*
* This will add the neccessary functionality for tooltips to be displayed
* for each control we create and display.
*
* @since 1.3.1
*/
api.Tooltips = {
/**
* Initializes BoldGrid SEO Tooltips.
*
* @since 1.3.1
*/
init : function () {
$( document ).ready( self.onReady );
},
/**
* Sets up event listeners and selector cache in settings on document ready.
*
* @since 1.3.1
*/
onReady : function() {
self.getSettings();
self.hideTooltips();
self._enableTooltips();
self._toggleTooltip();
},
/**
* Cache selectors
*
* @since 1.3.1
*/
getSettings : function() {
self.settings = {
description : $( '.butterbean-control .butterbean-description' ),
tooltip : $( '<span />', { 'class' : 'bgseo-tooltip dashicons dashicons-editor-help', 'aria-expanded' : 'false' }),
onClick : $( '.butterbean-label, .bgseo-tooltip' ),
};
},
/**
* Toggle Tooltips
*
* This sets up the event listener for clicks on tooltips or control labels,
* which will hide and show the description of the control for the user.
*
* @since 1.3.1
*/
_toggleTooltip : function() {
self.settings.onClick.on( 'click', function( e ) {
self.toggleTooltip( e );
});
},
/**
* Enables tooltips for any controls that utilize the description field.
*
* @since 1.3.1
*/
_enableTooltips : function() {
self.settings.description.prev().append( self.settings.tooltip );
},
/**
* This handles the toggle of the tooltip open/close.
*
* @param {Object} e Selector passed from click event.
*
* @since 1.3.1
*/
toggleTooltip : function( e ) {
$( e.currentTarget ).next( '.butterbean-description' ).slideToggle();
},
/**
* This hides all tooltips when api.Tooltips is initialized.
*
* @since 1.3.1
*/
hideTooltips : function() {
self.settings.description.hide();
},
};
self = api.Tooltips;
})( jQuery );
var BOLDGRID = BOLDGRID || {};
BOLDGRID.SEO = BOLDGRID.SEO || {};
( function ( $ ) {
'use strict';
var api;
api = BOLDGRID.SEO;
/**
* BoldGrid SEO Initialize.
*
* This initializes BoldGrid SEO.
*
* @since 1.3.1
*/
api.Init = {
/**
* Initialize Utilities.
*
* @since 1.3.1
*/
load : function () {
_.each( api, function( obj ) {
return obj.init && obj.init();
});
},
};
})( jQuery );
BOLDGRID.SEO.Init.load();