Skip to content
This repository has been archived by the owner on Oct 4, 2022. It is now read-only.

Commit

Permalink
Add an API to register custom analyzer tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Anton Timmermans committed Oct 27, 2015
1 parent a409e62 commit ddec6b7
Show file tree
Hide file tree
Showing 8 changed files with 360 additions and 6 deletions.
160 changes: 158 additions & 2 deletions dist/yoast-seo.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ YoastSEO = ( "undefined" === typeof YoastSEO ) ? {} : YoastSEO;
* @param {String} args.snippetMeta The meta description as displayed in the snippet preview.
* @param {String} args.snippetCite The URL as displayed in the snippet preview.
*
* @property {Object} analyses Object that contains all analyses.
*
* @constructor
*/
YoastSEO.Analyzer = function( args ) {
this.config = args;
this.checkConfig();
this.init( args );

this.analyses = {};
};

/**
Expand Down Expand Up @@ -123,16 +127,48 @@ YoastSEO.Analyzer.prototype.loadWordlists = function() {
* starts queue of functions executing the analyzer functions untill queue is empty.
*/
YoastSEO.Analyzer.prototype.runQueue = function() {
var output, score;

//remove first function from queue and execute it.
// Remove the first item from the queue and execute it.
if ( this.queue.length > 0 ) {
this.__output = this.__output.concat( this[ this.queue.shift() ]() );
var currentQueueItem = this.queue.shift();

if ( undefined !== this[ currentQueueItem ] ) {
output = this[ currentQueueItem ]();
} else if ( this.analyses.hasOwnProperty( currentQueueItem ) ) {
score = this.analyses[ currentQueueItem ].callable();

/*
* This is because the analyzerScorer requires this format and we want users that add plugins to just return
* a score because that makes the API easier. So this is a translation while our internal format isn't
* perfect.
*/
output = {
"name": this.analyses[ currentQueueItem ].name,
"result": score
};
}

this.__output = this.__output.concat( output );

this.runQueue();
} else {
this.score();
}
};

/**
* Adds an analysis to the analyzer
*
* @param {Object} analysis The analysis object.
* @param {string} analysis.name The name of this analysis.
* @param {function} analysis.callable The function to call to calculate this the score.
*/
YoastSEO.Analyzer.prototype.addAnalysis = function( analysis ) {
this.analyses[ analysis.name ] = analysis;
this.queue.push( analysis.name );
};

/**
* returns wordcount from the preprocessor storage to include them in the results.
* @returns {{test: string, result: (Function|YoastSEO.PreProcessor.wordcount|Number)}[]}
Expand Down Expand Up @@ -876,6 +912,21 @@ YoastSEO.AnalyzeScorer.prototype.totalScore = function() {
var totalAmount = scoreAmount * YoastSEO.analyzerScoreRating;
return Math.round( ( totalScore / totalAmount ) * 10 );
};

/**
* Adds a custom scoring to the analyzer scoring
*
* @param {Object} scoring
* @param {string} scoring.name
* @param {Object} scoring.scoring
*/
YoastSEO.AnalyzeScorer.prototype.addScoring = function( scoring ) {
var scoringObject = scoring.scoring;

scoringObject.scoringName = scoring.name;

this.scoring.push( scoringObject );
};
;/* jshint browser: true */
/* global YoastSEO: true */
YoastSEO = ( "undefined" === typeof YoastSEO ) ? {} : YoastSEO;
Expand Down Expand Up @@ -1249,8 +1300,12 @@ YoastSEO.App.prototype.runAnalyzer = function() {

if ( typeof this.pageAnalyzer === "undefined" ) {
this.pageAnalyzer = new YoastSEO.Analyzer( this.analyzerData );

this.pluggable._addPluginTests( this.pageAnalyzer );
} else {
this.pageAnalyzer.init( this.analyzerData );

this.pluggable._addPluginTests( this.pageAnalyzer );
}

this.pageAnalyzer.runQueue();
Expand Down Expand Up @@ -1341,13 +1396,15 @@ YoastSEO = ( "undefined" === typeof YoastSEO ) ? {} : YoastSEO;
* @property preloadThreshold {number} The maximum time plugins are allowed to preload before we load our content analysis.
* @property plugins {object} The plugins that have been registered.
* @property modifications {object} The modifications that have been registered. Every modification contains an array with callables.
* @property customTests {Array} All tests added by plugins.
*/
YoastSEO.Pluggable = function( app ) {
this.app = app;
this.loaded = false;
this.preloadThreshold = 3000;
this.plugins = {};
this.modifications = {};
this.customTests = [];

// Allow plugins 500 ms to register before we start polling their
setTimeout( this._pollLoadingPlugins.bind( this ), 1500 );
Expand Down Expand Up @@ -1400,6 +1457,30 @@ YoastSEO.App.prototype.registerModification = function( modification, callable,
return this.pluggable._registerModification( modification, callable, pluginName, priority );
};

/**
* Registers a custom test for use in the analyzer, this will result in a new line in the analyzer results. The function
* has to return a result based on the contents of the page/posts.
*
* The scoring object is a special object with definitions about how to translate a result from your analysis function
* to a SEO score.
*
* Negative scores result in a red circle
* Scores 1, 2, 3, 4 and 5 result in a orange circle
* Scores 6 and 7 result in a yellow circle
* Scores 8, 9 and 10 result in a red circle
*
* @param {string} name Name of the test.
* @param {function} analysis A function that analyzes the content and determines a score for a certain trait.
* @param {Object} scoring A scoring object that defines how the analysis translates to a certain SEO score.
* @param {string} pluginName The plugin that is registering the test.
* @param {number} priority (optional) Determines when this test is run in the analyzer queue. Is currently ignored,
* tests are added to the end of the queue.
* @returns {boolean}
*/
YoastSEO.App.prototype.registerTest = function( name, analysis, scoring, pluginName, priority ) {
return this.pluggable._registerTest( name, analysis, scoring, pluginName, priority );
};

/**************** DSL IMPLEMENTATION ****************/

/**
Expand Down Expand Up @@ -1524,6 +1605,47 @@ YoastSEO.Pluggable.prototype._registerModification = function( modification, cal
return true;
};

/**
* @private
*/
YoastSEO.Pluggable.prototype._registerTest = function( name, analysis, scoring, pluginName, priority ) {
if ( typeof name !== "string" ) {
console.error( "Failed to register modification for plugin " + pluginName + ". Expected parameter `name` to be a string." );
return false;
}

if ( typeof analysis !== "function" ) {
console.error( "Failed to register modification for plugin " + pluginName + ". Expected parameter `analyzer` to be a function." );
return false;
}

if ( typeof pluginName !== "string" ) {
console.error( "Failed to register modification for plugin " + pluginName + ". Expected parameter `pluginName` to be a string." );
return false;
}

// Validate origin
if ( this._validateOrigin( pluginName ) === false ) {
console.error( "Failed to register modification for plugin " + pluginName + ". The integration has not finished loading yet." );
return false;
}

// Default priority to 10
var prio = typeof priority === "number" ? priority : 10;

// Prefix the name with the pluginName so the test name is always unique.
name = pluginName + "-" + name;

this.customTests.push( {
"name": name,
"analysis": analysis,
"scoring": scoring,
"prio": prio
} );

return true;
};

/**************** PRIVATE HANDLERS ****************/

/**
Expand Down Expand Up @@ -1612,6 +1734,40 @@ YoastSEO.Pluggable.prototype._applyModifications = function( modification, data,

};

/**
* Adds new tests to the analyzer and it's scoring object.
*
* @param {YoastSEO.Analyzer} analyzer The analyzer object to add the tests to
* @private
*/
YoastSEO.Pluggable.prototype._addPluginTests = function( analyzer ) {
this.customTests.map( function( customTest ) {
this._addPluginTest( analyzer, customTest );
}, this );
};

/**
* Adds one new test to the analyzer and it's scoring object.
*
* @param {YoastSEO.Analyzer} analyzer
* @param {Object} pluginTest
* @param {string} pluginTest.name
* @param {function} pluginTest.callable
* @param {Object} pluginTest.scoring
* @private
*/
YoastSEO.Pluggable.prototype._addPluginTest = function( analyzer, pluginTest ) {
analyzer.addAnalysis( {
"name": pluginTest.name,
"callable": pluginTest.analysis
} );

analyzer.analyzeScorer.addScoring( {
"name": pluginTest.name,
"scoring": pluginTest.scoring
} );
};

/**
* Strips modifications from a callChain if they were not added with a valid origin.
*
Expand Down
4 changes: 2 additions & 2 deletions dist/yoast-seo.min.js

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions example/example-plugin-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
(function() {
addEventListener( "load", function() {
// Wait for YoastSEO to be loaded
setTimeout(function() {
addPlugin();
}, 0);
});

function addPlugin() {
YoastSEO.app.registerPlugin( "example-plugin", { "status": "ready" } );
YoastSEO.app.registerTest( "example-test", function() {
return ( Math.random() * 100 );
}, {
scoreArray: [
{
min: 0,
max: 25,
score: -1,
text: "It's bad!"
},
{
min: 25,
max: 50,
score: 4,
text: "It's mediocre!"
},
{
min: 50,
max: 75,
score: 7,
text: "It's ok!"
},
{
min: 75,
score: 9,
text: "It's all good!"
}
]
}, "example-plugin" );
}
}());
1 change: 1 addition & 0 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<link rel="stylesheet" href="style.min.css">

<script type="text/javascript" src="example-scraper.min.js"></script>
<script type="text/javascript" src="example-plugin-test.js"></script>
<script type="text/javascript" src="../dist/yoast-seo.js"></script>


Expand Down
40 changes: 38 additions & 2 deletions js/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ YoastSEO = ( "undefined" === typeof YoastSEO ) ? {} : YoastSEO;
* @param {String} args.snippetMeta The meta description as displayed in the snippet preview.
* @param {String} args.snippetCite The URL as displayed in the snippet preview.
*
* @property {Object} analyses Object that contains all analyses.
*
* @constructor
*/
YoastSEO.Analyzer = function( args ) {
this.config = args;
this.checkConfig();
this.init( args );

this.analyses = {};
};

/**
Expand Down Expand Up @@ -123,16 +127,48 @@ YoastSEO.Analyzer.prototype.loadWordlists = function() {
* starts queue of functions executing the analyzer functions untill queue is empty.
*/
YoastSEO.Analyzer.prototype.runQueue = function() {
var output, score;

//remove first function from queue and execute it.
// Remove the first item from the queue and execute it.
if ( this.queue.length > 0 ) {
this.__output = this.__output.concat( this[ this.queue.shift() ]() );
var currentQueueItem = this.queue.shift();

if ( undefined !== this[ currentQueueItem ] ) {
output = this[ currentQueueItem ]();
} else if ( this.analyses.hasOwnProperty( currentQueueItem ) ) {
score = this.analyses[ currentQueueItem ].callable();

/*
* This is because the analyzerScorer requires this format and we want users that add plugins to just return
* a score because that makes the API easier. So this is a translation while our internal format isn't
* perfect.
*/
output = {
"name": this.analyses[ currentQueueItem ].name,
"result": score
};
}

this.__output = this.__output.concat( output );

this.runQueue();
} else {
this.score();
}
};

/**
* Adds an analysis to the analyzer
*
* @param {Object} analysis The analysis object.
* @param {string} analysis.name The name of this analysis.
* @param {function} analysis.callable The function to call to calculate this the score.
*/
YoastSEO.Analyzer.prototype.addAnalysis = function( analysis ) {
this.analyses[ analysis.name ] = analysis;
this.queue.push( analysis.name );
};

/**
* returns wordcount from the preprocessor storage to include them in the results.
* @returns {{test: string, result: (Function|YoastSEO.PreProcessor.wordcount|Number)}[]}
Expand Down
15 changes: 15 additions & 0 deletions js/analyzescorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,18 @@ YoastSEO.AnalyzeScorer.prototype.totalScore = function() {
var totalAmount = scoreAmount * YoastSEO.analyzerScoreRating;
return Math.round( ( totalScore / totalAmount ) * 10 );
};

/**
* Adds a custom scoring to the analyzer scoring
*
* @param {Object} scoring
* @param {string} scoring.name
* @param {Object} scoring.scoring
*/
YoastSEO.AnalyzeScorer.prototype.addScoring = function( scoring ) {
var scoringObject = scoring.scoring;

scoringObject.scoringName = scoring.name;

this.scoring.push( scoringObject );
};
4 changes: 4 additions & 0 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,12 @@ YoastSEO.App.prototype.runAnalyzer = function() {

if ( typeof this.pageAnalyzer === "undefined" ) {
this.pageAnalyzer = new YoastSEO.Analyzer( this.analyzerData );

this.pluggable._addPluginTests( this.pageAnalyzer );
} else {
this.pageAnalyzer.init( this.analyzerData );

this.pluggable._addPluginTests( this.pageAnalyzer );
}

this.pageAnalyzer.runQueue();
Expand Down
Loading

0 comments on commit ddec6b7

Please sign in to comment.