yoastseo-dep
Version:
Yoast clientside page analysis
229 lines (209 loc) • 7.55 kB
JavaScript
import { __, _n, sprintf } from "@wordpress/i18n";
import { merge } from "lodash-es";
import { getSubheadingsTopLevel } from "../../../languageProcessing/helpers/html/getSubheadings";
import Assessment from "../assessment";
import { createAnchorOpeningTag } from "../../../helpers/shortlinker";
import { inRangeStartEndInclusive } from "../../helpers/assessments/inRange.js";
import AssessmentResult from "../../../values/AssessmentResult";
/**
* Represents the assessment that checks if the keyword is present in one of the subheadings.
*/
export default class SubHeadingsKeywordAssessment extends Assessment {
/**
* Sets the identifier and the config.
*
* @param {object} config The configuration to use.
*
* @returns {void}
*/
constructor( config = {} ) {
super();
const defaultConfig = {
parameters: {
lowerBoundary: 0.3,
upperBoundary: 0.75,
},
scores: {
noMatches: 3,
tooFewMatches: 3,
goodNumberOfMatches: 9,
tooManyMatches: 3,
},
urlTitle: createAnchorOpeningTag( "https://yoa.st/33m" ),
urlCallToAction: createAnchorOpeningTag( "https://yoa.st/33n" ),
};
this.identifier = "subheadingsKeyword";
this._config = merge( defaultConfig, config );
}
/**
* Runs the matchKeywordInSubheadings research and based on this returns an assessment result.
*
* @param {Paper} paper The paper to use for the assessment.
* @param {Researcher} researcher The researcher used for calling research.
*
* @returns {AssessmentResult} The assessment result.
*/
getResult( paper, researcher ) {
this._subHeadings = researcher.getResearch( "matchKeywordInSubheadings" );
const assessmentResult = new AssessmentResult();
this._minNumberOfSubheadings = Math.ceil( this._subHeadings.count * this._config.parameters.lowerBoundary );
this._maxNumberOfSubheadings = Math.floor( this._subHeadings.count * this._config.parameters.upperBoundary );
const calculatedResult = this.calculateResult();
assessmentResult.setScore( calculatedResult.score );
assessmentResult.setText( calculatedResult.resultText );
return assessmentResult;
}
/**
* Checks whether the paper has a subheadings.
*
* @param {Paper} paper The paper to use for the check.
*
* @returns {boolean} True when there is at least one subheading.
*/
hasSubheadings( paper ) {
const subheadings = getSubheadingsTopLevel( paper.getText() );
return subheadings.length > 0;
}
/**
* Checks whether the paper has a text and a keyword.
*
* @param {Paper} paper The paper to use for the assessment.
*
* @returns {boolean} True when there is text and a keyword.
*/
isApplicable( paper ) {
return paper.hasText() && paper.hasKeyword() && this.hasSubheadings( paper );
}
/**
* Checks whether there are too few subheadings with the keyphrase.
*
* This is the case if the number of subheadings with the keyphrase is more than 0 but less than the specified
* recommended minimum.
*
* @returns {boolean} Returns true if the keyphrase is included in too few subheadings.
*/
hasTooFewMatches() {
return this._subHeadings.matches > 0 && this._subHeadings.matches < this._minNumberOfSubheadings;
}
/**
* Checks whether there are too many subheadings with the keyphrase.
*
* The upper limit is only applicable if there is more than one subheading. If there is only one subheading with
* the keyphrase this would otherwise always lead to a 100% match rate.
*
* @returns {boolean} Returns true if there is more than one subheading and if the keyphrase is included in less
* subheadings than the recommended maximum.
*/
hasTooManyMatches() {
return this._subHeadings.count > 1 && this._subHeadings.matches > this._maxNumberOfSubheadings;
}
/**
* Checks whether there is only one higher-level subheading and this subheading includes the keyphrase.
*
* @returns {boolean} Returns true if there is exactly one higher-level subheading and this
* subheading has a keyphrase match.
*/
isOneOfOne() {
return this._subHeadings.count === 1 && this._subHeadings.matches === 1;
}
/**
* Checks whether there is a good number of subheadings with the keyphrase.
*
* This is the case if there is only one subheading and that subheading includes the keyphrase or if the number of
* subheadings with the keyphrase is within the specified recommended range.
*
* @returns {boolean} Returns true if the keyphrase is included in a sufficient number of subheadings.
*/
hasGoodNumberOfMatches() {
return inRangeStartEndInclusive(
this._subHeadings.matches,
this._minNumberOfSubheadings,
this._maxNumberOfSubheadings
);
}
/**
* Determines the score and the Result text for the subheadings.
*
* @returns {Object} The object with the calculated score and the result text.
*/
calculateResult() {
if ( this.hasTooFewMatches() ) {
return {
score: this._config.scores.tooFewMatches,
resultText: sprintf(
/* translators: %1$s and %2$s expand to a link on yoast.com, %3$s expands to the anchor end tag. */
__(
"%1$sKeyphrase in subheading%3$s: %2$sUse more keyphrases or synonyms in your H2 and H3 subheadings%3$s!",
"wordpress-seo"
),
this._config.urlTitle,
this._config.urlCallToAction,
"</a>"
),
};
}
if ( this.hasTooManyMatches() ) {
return {
score: this._config.scores.tooManyMatches,
resultText: sprintf(
/* translators: %1$s and %2$s expand to a link on yoast.com, %3$s expands to the anchor end tag. */
__(
// eslint-disable-next-line max-len
"%1$sKeyphrase in subheading%3$s: More than 75%% of your H2 and H3 subheadings reflect the topic of your copy. That's too much. %2$sDon't over-optimize%3$s!",
"wordpress-seo"
),
this._config.urlTitle,
this._config.urlCallToAction,
"</a>"
),
};
}
if ( this.isOneOfOne() ) {
return {
score: this._config.scores.goodNumberOfMatches,
resultText: sprintf(
/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag,
%3$d expands to the number of subheadings containing the keyphrase. */
__(
"%1$sKeyphrase in subheading%2$s: Your H2 or H3 subheading reflects the topic of your copy. Good job!",
"wordpress-seo"
),
this._config.urlTitle,
"</a>",
this._subHeadings.matches
),
};
}
if ( this.hasGoodNumberOfMatches() ) {
return {
score: this._config.scores.goodNumberOfMatches,
resultText: sprintf(
/* translators: %1$s expands to a link on yoast.com, %2$s expands to the anchor end tag,
%3$d expands to the number of subheadings containing the keyphrase. */
_n(
"%1$sKeyphrase in subheading%2$s: %3$s of your H2 and H3 subheadings reflects the topic of your copy. Good job!",
"%1$sKeyphrase in subheading%2$s: %3$s of your H2 and H3 subheadings reflect the topic of your copy. Good job!",
this._subHeadings.matches,
"wordpress-seo"
),
this._config.urlTitle,
"</a>",
this._subHeadings.matches
),
};
}
return {
score: this._config.scores.noMatches,
resultText: sprintf(
/* translators: %1$s and %2$s expand to a link on yoast.com, %3$s expands to the anchor end tag. */
__(
"%1$sKeyphrase in subheading%3$s: %2$sUse more keyphrases or synonyms in your H2 and H3 subheadings%3$s!",
"wordpress-seo"
),
this._config.urlTitle,
this._config.urlCallToAction,
"</a>"
),
};
}
}